I do this all the time. I call it Service Oriented Monolith. I.e. you use the principle of micro-services in a single code base. It works pretty well because micro-services gives you clear segregation.
Not the person you replied to, but I do the same thing and by default no.
So the definition of what a service is shrinks down really to a deployment artefact; each "service" is deployed individually as its own process (container). All shared data is persisted via a single REST-style API service. We nearly went all the way and deployed PostgREST for this but some specifics around security and a few other aspects stopped us. But in spirit, that is how it is architected : data persistence is a service that all the other "services" use.
The nice thing about this is you do still get a lot of the upsides. Because deployment is decoupled, you don't to have everybody constrained to the same development cycle. Service A can stay pinned at a specific version while service B is advanced to hotfix a critical change. Meanwhile Service C isn't blocked from deployment because Service A's tests are still failing. And we don't have to (necessarily) roll back Service D if prod deployment of Service E failed. etc etc.
I wouldn't. I would group them in logical groups, with the principle of moving complexity closer to where the data is.
A "Users" service might have crud operations, and maybe a few report generators that can run SQL against the Users database. But the "Users" services should not have access to "Account Balance" service, e.g. Then say, the KYC service can access the Users and the Account Balance services and then do a manual join on the data.
But say if KYC is directly accessing the Users database and the User table schema changes drastically, not only do you have to change the Users services, but you have to change KYC and coordinate KYC to deploy their changes at the same time your database changes.
The big problem is if the coupling is too tight, then it's hard to refactor in the future. OTOH if the coupling is too loose, then you might be making lots of overhead in computing when you could drastically simplify it by a tighter coupling of code.
The right answer I believe is to move the complexity close to where the data is. So maybe there is some complex KYC calculation on the Users service that is trivially solved by a custom SQL statement on the Users table. In this case it should probably be in the Users service even if it's only used by KYC, if this makes sense.
To put it differently, logical separation of state doesn't have to mean physical separation of state. One Postgres instance can hold your whole app's state -- just, put each service's data into a separate database within Postgres. I've done this and I like it a lot.
Well, I don't think you can have transactions open across logical databases any more than you can join across them. And you might anyway design the relevant component to serve multiple clients simultaneously, so you could still be executing multiple simultaneous transactions.
The component is the sole owner of its state. How it interacts with that state is an internal concern.
Comments
I do this all the time. I call it Service Oriented Monolith. I.e. you use the principle of micro-services in a single code base. It works pretty well because micro-services gives you clear segregation.
Does each module get its own separate data store? (Genuine question as I see some micro services with direct access to shared data.)
Not the person you replied to, but I do the same thing and by default no.
So the definition of what a service is shrinks down really to a deployment artefact; each "service" is deployed individually as its own process (container). All shared data is persisted via a single REST-style API service. We nearly went all the way and deployed PostgREST for this but some specifics around security and a few other aspects stopped us. But in spirit, that is how it is architected : data persistence is a service that all the other "services" use.
The nice thing about this is you do still get a lot of the upsides. Because deployment is decoupled, you don't to have everybody constrained to the same development cycle. Service A can stay pinned at a specific version while service B is advanced to hotfix a critical change. Meanwhile Service C isn't blocked from deployment because Service A's tests are still failing. And we don't have to (necessarily) roll back Service D if prod deployment of Service E failed. etc etc.
I wouldn't. I would group them in logical groups, with the principle of moving complexity closer to where the data is.
A "Users" service might have crud operations, and maybe a few report generators that can run SQL against the Users database. But the "Users" services should not have access to "Account Balance" service, e.g. Then say, the KYC service can access the Users and the Account Balance services and then do a manual join on the data.
But say if KYC is directly accessing the Users database and the User table schema changes drastically, not only do you have to change the Users services, but you have to change KYC and coordinate KYC to deploy their changes at the same time your database changes.
The big problem is if the coupling is too tight, then it's hard to refactor in the future. OTOH if the coupling is too loose, then you might be making lots of overhead in computing when you could drastically simplify it by a tighter coupling of code.
The right answer I believe is to move the complexity close to where the data is. So maybe there is some complex KYC calculation on the Users service that is trivially solved by a custom SQL statement on the Users table. In this case it should probably be in the Users service even if it's only used by KYC, if this makes sense.
This is similar to the premise of "Righting software" and volatility based decomposition as opposed to functional decomposition.
Your example is functional but you wouldnt have to change much to have it fit in the "engines and managers" pattern the author is fond of.
Yes. But in a single database instance. The Service Oriented Monolith also applies to the database.
To put it differently, logical separation of state doesn't have to mean physical separation of state. One Postgres instance can hold your whole app's state -- just, put each service's data into a separate database within Postgres. I've done this and I like it a lot.
Guessing you don't need atomic DB transactions in that case.
Well, I don't think you can have transactions open across logical databases any more than you can join across them. And you might anyway design the relevant component to serve multiple clients simultaneously, so you could still be executing multiple simultaneous transactions.
The component is the sole owner of its state. How it interacts with that state is an internal concern.