It's all about the data
Many prominent computer scientists can be quoted saying something like this. For example, of building Git, Linus Torvalds said:
Bad programmers worry about the code. Good programmers worry about
data structures and their relationships.
If you don't have the data model figured out, you will be continuously refactoring your microservices, as you continue to change your data model.
So start with a matured data model. Review it, try to break it with edge cases; then move on to "code".
CRUD then Orchestration
CRUD services are those that are for INSERT,READ,UPDATE,DELETE. They are tightly correlated to a table/collection. These are actually the closest to the academic definition of microservices: "own data".
However, information arises from relationships between data. Orchestration services are a layer of microservices that may be designed for particular tasks. These don't need to "call" the CRUD services, particularly if you already can enforce data-access rules in the data-layer.
Where should I store what plan a user has (Users vs Plans
microservices)?
This question arises when designers limit themselves to "CRUD Service" thinking. You need a orchestration services.
Subscribing to a plan, and Downloading a file, probably need to interact with a few table/collection in your database. They would be orchestration.
Microprocess discipline will help
Microservices are not a well-defined discipline, but Microprocess Architecture is. see https://colossal.gitbook.io/microprocess/comparisons/compared-to-microservices.
There are no CRUD microservices - your client application can directly query the database. There are no Orchestration microservices - you can model these as data.
Ideally you would conceptually model a "FileDownload" entity, PlanSubscribe, and more directly in your database model.
Model:
- Users
- DownloadsRequest - when a user selects an item to download, this record is created. The user is allowed to INSERT into this table.
- DownloadTokens - a microprocess creates this when there is a DownloadRequest record without a DownloadToken, and if the user hasn't gone over monthly limit. The user is allowed to SELECT from a view of this table where the UserID field is themself.
- DownloadLogs - when the file download occurs, the system creates this record. This is used to count how many successful downloads occur. It's possible to log Download-Start and Download-End stages.
- Plans - there are many ways that plans could be limited. This is just the placeholder for the name.
- PlanDownloadLimits - limits should be modelled in separate tables.
Then you just mutate data without worrying about "service" call structures.
Example Pseudo-code Microprocess for Download Tokens:
foreach R in (select R.*
from DownloadsRequest R
where not exists (select 1 from DownloadTokens T where T.RequestID = R.ID) -- Not yet created
and exists (select 1 from PlanDownloadLimits L where L.UserID = R.UserID and L.IsActive=1 and MonthlyLimit > 0) -- Quick check that they are on a valid plan)
#Downloads = (select count(*) from DownloadLogs O where O.UserID = R.UserID and year(O.Started)=year(now()) and month(O.Started)=month(now()))
#Limit = (select count(*) from DownloadLogs O where O.UserID = R.UserID and year(O.Started)=year(now()) and month(O.Started)=month(now()))
if (#Downloads < #Limit)
INSERT INTO DownloadTokens (..) values (R.UserID, R.DownloadID, R.Download.URL, SecureRandomForToken())
else
Update DownloadsRequest set Denied=1 where ID = R.ID
If you don't want to use the "Microprocess" discipline, that's fine. But you will at least obtain a more complete conceptual model by thinking this way.