I'm trying to design a new microservice architecture that will eventually replace our current two monolithic APIs (we develop a desktop payroll software that uses our API to send payroll documents to employees or have them sign contracts, and more features and front-ends to come).
There is a lot of complexity regarding users relationships and I'd like to future-proof my design as much as possible.
Please excuse me if I'm not clear enough, I'm not a native English speaker and there are a lot of fine details :)
TL;DR
I will give many details below in case you're interested, but here is a summary:
- I have many types of actors who are tied with chained, business logic relationships (ex:
Organization > Company > Employee
). - Most of (maybe all in the future) these actors are candidates for being linked to a web user account. And a user account should be able to act like multiple actors (ex: an account
X
could act as aCompany A
, anEmployee from Company A
and anEmployee from Company B
).
And I hesitate between at least 2 solutions:
1) Creating a microservice per actor type
- This could make sense because each actor have its own business logic (properties, actions).
- But I'm afraid it will generate a lot of inter-services requests for basic actions (ex:
DocumentService
must check if the user account has rights to sendDocuments
to anEmployee
, and this permission is defined as aCompany
business logic...). - Moreover, each microservice should be aware of others business logic, which greatly defeats the microservice philosophy in my opinion.
Option 1
2) Creating a generic UserManagement service
- This could also make sense because I feel "actors and users relationships" could be seen as a business logic in itself, and all these actors are tightly coupled.
- My main fear with this option is that this service could become bigger and bigger as we bring features to the web. But then again I could create new microservices for these features and keep using my UserManagementService as a sort of RBAC provider.
Option 2
What sounds more like a future-proof, best practice to you?
Do you have any advice or thoughts?
More details
Currently, these are the different types of actors in our system:
Types of actors
Please note these edge (but nonetheless common) cases:
- A
Customer
could be an accounting firm and manage payrolls for multipleCompanies
. - A
Customer
could be managing itsCompany
alone, so all these actors could be one single person. And the Customer could be one of their ownEmployees
as well. Employees
, being artists for the most part, often work for severalCompanies
(which could be managed by differentOrganizations
).
This is basically how our software works (locally, without APIs):
Customer
pays for their licence.Customer
provides licence key to create anOrganization
(can create multiple ones).Customer
createsSoftwareUsers
inside thisOrganization
, with different access levels (which determines which companies/employees one can read/write).SoftwareUsers
can createCompanies
inside thisOrganization
to manage their payroll.SoftwareUsers
can createEmployees
attached to a singleCompany
.SoftwareUsers
can generate payslips forEmployees
.
Then, these are the web-based features that are implemented by our current APIs:
SoftwareUser
can invite anEmployee
to create their web account (on behalf of their attachedCompany
).SoftwareUser
can send PDFDocuments
to anEmployee
's account (on behalf of their attachedCompany
).SoftwareUser
can create aCampain
to have anEmployee
sign a contract online (on behalf of their attachedCompany
).
As for future features, there will be:
Customer
can pay for their licence online and view some data (remaining payroll volume, consumed phone support time, etc...).Companies
(who are not using the software if theCustomer
is an accountant firm) can viewEmployees
related to them, signedCampains
andDocuments
generated on their behalf.SoftwareUsers
who don't have access to the desktop software can enter data to populate payslips (this "feature" is expected to grow in the next years as we will progressively bring all the software's features to the web).
Finally, this system needs to be flexible, considering the following use cases:
Companies
could be transferred to otherOrganizations
(ex: a company could ask its accountant to provide its data if they wanted to manage their payroll itself).- The same principle applies to each actor type.
Account
creation invitations (withDocuments
attached) could be sent to wrong email addresses, so we must be able to transfer anEmployee
to a different account.
These are the reason I was planning this for relationships and authentication:
- For relationships, each actor will only know its parent ID and its linked
Account(s)
ID(s): this will enable to easily switch owners (a required feature). - For authentication, I'm planning to use JWTs with only the
Account
ID as payload.
UPDATE
I took your comments into consideration and talked with some teammates, here is a new option:
3) With a PermissionService
Accounts
are not linked to "actors" anymore. Instead, they are linked to multiplePermissions
that each specifies who has access to which resource, and possibly additional claims (ex:accessLevel
for aPermission
on anOrganization
).This gives the possibility to provide either "account tokens" (with only the account ID), or "permission tokens" (with also the target ID and claims), depending on whether we enable SSO or need restricted service accounts.
As for the
ActorService
(not sure if the name fits well), its purpose is to hold the business logic regarding "actors relationships" as well as their properties (some of which were added on the diagram to illustrate their business value).
Option 3
I'm still not 100% sure about this design, so every comment is welcome.
3 Answers 3
Forget about the data. Systems need to be modeled according to behavior if you want any chance at something even remotely maintainable at scale.
Your diagrams only show the data your system is using, and it is confusing you because it is leading you to consider lines (arrows) of communication between entities that are actually all the same thing. Your data suggests that an Employee
is a Customer
(it carries a Customer._id
either through a bunch of joins or explicitly depending on your database structure). Is that true? Don't answer that.
What I am getting at above is that you are mixing authorization into your schema. You have diagrammed five database tables that, together, hold only one single field of data that isn't a key ([accessLevel]
). What if I proposed you replace all of your tables that act as a proxy for a human being with a single table: User [_id, accessLevel]
?
Instead of worrying about how a Customer
can interact with an Employee
or whether a SoftwareUser
is part of an Organization
, just list out the use-cases for your application and implement them in a way that is completely agnostic as to "who" is invoking them.
Modeling the "Actors" within a domain, that is attempting to draw a line between the person that is operating the machine and each domain method, is going to lead to a world of pain and complexity. This is because there is always a person (or some proxy thereof) invoking each command, which means every single operation in your system is going to be complected with (dependent to) all of this information. It is no wonder that authorization is best left out of the domain.
To summarize, I would remove Customer
and SoftwareUser
entirely and replace them with a more traditional user/roles authorization schema (notably not part of your domain). The only value they add is as a proxy for "the person sitting at the computer" (look at your use-cases). This greatly simplifies your system.
As to how to organize the remaining Organization
->Company
->Employee
hierarchy? These need to be on one box (unless to are okay with an eventually-consistent model).
-
Thank you for your comment, this is really interesting. I updated my post with a 3rd option using permissions, would you mind taking a look? I kept the
Customer
table because it is a business entity and not just a user.Seeven– Seeven05/12/2020 09:15:05Commented May 12, 2020 at 9:15 -
@Seeven I don't know that I would separate the authorization into 3 distinct services (are you boundaries meant to delineate "microservices"?), but otherwise your updated design looks fine to me.user3347715– user334771505/12/2020 18:34:52Commented May 12, 2020 at 18:34
I think you are making it unnecessary complex by linking Organisation
and Company
to login accounts. Both are legal entities that can not actually take an action themselves. They need a human to act on their behalf and you have such a human already identified in your system as SoftwareUser
.
You can even take it a step further and conceptually separate the login account of an Employee from the Employee
record that is owned by a Company
. The only link that you actually need between the two is in the permissions (the "Login-Employee" is only allowed to access the "Company-Employee" documents) and maybe a back-link for sending notifications. That separation could even allow for the situation that a Company-Employee is declared legally incompetent and their guardian (also) has to be allowed access to the records.
-
Thank you for your comment, I get your point. I updated my post with a 3rd option using permissions and separating user accounts from the actors, would you mind taking a look?Seeven– Seeven05/12/2020 09:21:12Commented May 12, 2020 at 9:21
-
@Seeven, your third option looks very reasonable. It also nicely separates the domain side (the things that the business talks about regardless of how it gets implemented) from the application side (the things that need to exist due to the choice for a computer-based implementation, like logins).Bart van Ingen Schenau– Bart van Ingen Schenau05/12/2020 12:14:02Commented May 12, 2020 at 12:14
I wouldn't do either of those things.
If you want to create a maintainable (i.e. future-proof) system you should not rely on details like data, and instead rely on abstract business concepts.
This is sometimes called vertical-, or feature-based splitting. This is the exact opposite of what you seem to be doing, which is basically having a microservice per data table.
Each microservice should implement specific business use-cases, and request-response calls between microservices should be minimized, ideally non-existent. If you use it in a web-context, integration can be easily done on the web-client (links, forms) instead of direct calls. Each microservice should own a complete process of some business-case.
This is the only way you can be sure that changes stay mostly localized and knowledge won't pile up in one or two "central" microservices.
To be clear, ideally microservices should contain/provide everything they need, the database, their UI, their authentication, etc. Of course you can be trade-offs here and there. But the more independent and self-contained you can get your microservices, the more easy it will be to maintain them later.
Explore related questions
See similar questions with these tags.
CompanyService
a microservice that is going to orchestrate calls to other services or is it simply evaluating authorizations? In the second proposal, is theUserManagement
service again orchestrating calls?