Breaking a monolith up into proper microservices with appropriate boundaries for your domain is certainly more of an art than a science. The prerequisite to taking on such a task is a thorough understanding of your domain and the interactions within, and you won't get it right the first time. One of points that Evans makes in his book on Domain-Driven Design is that for any sufficiently complex domain, the domain model continually evolves because your understanding of the domain is continually evolving; you will understand it a little better tomorrow than you do today. That said, don't be afraid to start when you have an understanding that is "good enough" and be willing to adapt/evolve your model.
I don't know your domain, but it sounds to me like you need to first figure out in which bounded context Customer
primarily belongs. Yes, you want to minimize duplication of domain logic, and though it may not fit completely and neatly into a single service, to the extent that you make one service take primary responsibility for accessing, persisting, manipulating, validating, and ensuring the integrity of a Customer
, the better off you'll be.
From your question, I see two possibilities:
- The
Account Services
bounded context is the primary stakeholder in Customer
, and Customer
has non-trivial ties to other Account Services
entities and services. It's difficult to draw clear boundaries around a Customer
in isolation. In this case, Customer
belongs in the Account Services
bounded context.
Customer
is an independent enough concept to merit its own microservice. A Customer
can stand alone. In this case, Customer
belongs in its own bounded context.
In either case, great care should be taken to ensure that the Customer
-specific domain logic stays centralized in the Customer
microservice behind strong boundaries. Other services might use Customer
, or perhaps a light-weight (even read-only) CustomerView
, but their interactions should go through the Customer
service to the extent that they can.
In your question, you indicate that the Payments
bounded context will need access to Customer
, but it might just need a light-weight version. It should communicate with the Customer
service to get that light-weight object. If, during Payments processing you need to update the Customer
's billing address for example, Payments
should call into the Customer
microservice telling it to update its billing address. Payments
need not know anything about how to update a Customer
's billing address other than the single API call; any domain logic, validation, firing of domain events, etc... that need to happen as part of that operation are contained within the Customer
microservice.
Regarding your second question: it's true that atomic transactions become more complex/difficult in a distributed architecture. Do some reading on the Saga pattern: https://blog.couchbase.com/saga-pattern-implement-business-transactions-using-microservices-part/. Also, Jimmy Bogard is currently in the midst of a blog series called
Life Beyond Distributed Transactions: An Apostate's Implementation that may offer some good insights.
Hope this helps!