DDD. Shared kernel? Or pure event-driven microservices?
Asked Answered
B

4

14

I'm breaking my system into (at least) two bounded-contexts: study-design and survey-planning.

There's a concept named "subject" (potential subject for interviewing) in the study-design context. We also maintain associations between subjects and populations in that domain.

Now, in the survey-planning, we also need (some) information about the subject (for example: for planning a visit, or even for anticipated selection of questionnaire, in case the population the subject belongs to is known beforehand).

So, I need that "subject" in both contexts.

What approach should I pick? Having a shared kernel, as explained in Eric Evans DDD book? I don't mind (at least for now) having the two contexts sharing the same database.

enter image description here

Or... should I go pure microservice? Meaning: those two can't / shouldn't share database..., and in that case I might have to go the mirroring / duplicating route through event passing: https://www.infoq.com/news/2014/11/sharing-data-bounded-contexts

Any thoughts on which one is better, for the above situation?

Thanks!

Bernoulli answered 2/3, 2017 at 18:10 Comment(1)
Probably related: #17917222 Almost all answerers opted for not having shared-kernel. However, before I jump on the event-passing route (with its extra complexities), I'd like to hear some thoughts that could sway me toward shared-kernel, because I just want to be pragmatic. Thanks.Bernoulli
D
8

The context for microservices is distributed systems. In any other situation it would probably be overkill. Shared kernel will eventually split. That is usually the case. You may start from it. Nothing wrong with that. However, it will not stay there.

Dameron answered 3/3, 2017 at 6:8 Comment(6)
And duplication is never really a problem nowadays, right? I mean, having the the subjects copied (through events) from study-design domain to survey-planning domain is perfectly OK (and this would be consistent with microservice principle of being "independent"), right?Bernoulli
Bounded Context is defined by Ubiquitous Language. So what matters is a semantic difference - not how it is named. Semantic duplication will lead to problems as any violation of SRP (Single Responsibility Principle). When concepts are crossing BC(Bounded Context) boundaries (via events emission/consumption) Bounded Context ACL deals with it. Microservice does not share its model with anything so is self-contained and isolated.Dameron
Thanks for the advices; andyes @Sergiy, I also have been thinking about that anti-corruptio layer to translate events cross between domains. However now I'm weighing of unifiying my 3 bc into one, and have them into one single BC (and they will just be called business-component within that single BC). Looking for some discussions on stackoverflow before making final decision on this.Bernoulli
@CokordaRaka translating domain events in fact does not make sense due to events nature. They are facts of the past of other BCs. You may listen to them, they may trigger other actions (or not). They are immutable. The exercise of Context Mapping usually happens first. Practically, you do not collapse BCs. More often you segregate. Unless you defined those BCs wrong in the first place. BC is a component already. Try not to create superfluous abstraction.Dameron
Hi Sergiy, Not sure I understood your first sentence. I always thought that ACL is about translating/transforming events. Kind of adapter. Am I missing something / understood it wrongly?Bernoulli
@CokordaRaka sorry for not being clear. What I meant is that the term translation for the events is kinda misleading. You basically react to the event and ACL makes sure that whatever you create as a result is not corrupted by the concepts that are alien to the BC you handling event in. So I do not think you missed or misunderstood anything.Dameron
A
7

I recommend that you choose a event-driven solution, but not necessarily to use microservices. You could build an event-driven monolith in order to spend much less time on synchronizing the two models. When the application grows too big then you split the monolith into microservices. You could use CQRS to split event more the models into write and read. If you use event-sourcing things get even more interesting.

In my experience, with shared kernel, the models become god objects, one-size-fits-all kind of objects.

Ado answered 2/3, 2017 at 18:52 Comment(3)
Hi, thanks for the answer. Somewhat related: I also have this notion of context-mapping (e.g.: a "study" in study-design has a counterpart named "survey-plan" in survey-planning). My question: is it ok to have a pointer in the survey-plan pointing toward study, albeit only as ID (id of the survey)? Just for cross-referencing purposes.Bernoulli
By ID is always permitted. By reference to instance only inside an Aggregate.Ado
Agree with Constantin. @CokordaRaka As a word of caution, try to avoid semantic crossreferences. It may complicate the maintenance since it is semantic coupling anyways.Dameron
R
3

In my opinion, you have three entities:

  • study
  • survey
  • person

It is pretty intuitive to see that each of these is its own aggregate root. So then we are talking about inter-root relationships. In my experience, those are meaningful entities on their own, and cleanest and most future proof by far is to treat those relationships as independent aggregate roots.

The relationship between a study and a person is perhaps called TestSubject, and the relationship between a person and a survey could be called Interviewee or something similar. In another context, the person could be an employee for a company, and then the Employee would be its own aggregate root. Information that only relates to the relationship and not to the person or the study say, should be limited to this relationship specific aggregate root. This could for instance be the start date at which the subject started to take part in the test, and the end date (when he dropped out, if he or she dropped out prematurely, etc.)

As for storage, all aggregate roots should define their own separate repositories as interfaces and know only those interfaces, but the implementation of those interfaces is free to choose to use the same database or different ones, or even different kinds, local or distributed, etc. So this holds for these 'relational' aggregate roots as well. But you should almost force yourself to use different databases and preferably even different technologies (e.g. one EntityFramework, the other MongoDb) when you start with this, to force yourself to make sure your interfaces are properly defined and independent of implementation.

And yes, big fan of CQRS as well here, and Event/Command Sourcing as well. There are many light-weight implementations possible that allow you to upscale, but are very easy to get into and afford you almost completely linear (=predictable) complexity.

Raft answered 5/3, 2017 at 21:19 Comment(1)
Hi Arwin, thanks for the input; yes, I actually break down a study to multiple target population (we have cases where we'd like to sample from more than one populations in single study), and the TestSubject you mentioned would be "PopulationMembership" in my model. Initially my idea was to have 3 bounded-contexts: study-design, survey-planning, and survey-tracking. After some consideration and this: https://mcmap.net/q/830169/-size-of-a-bounded-context I decided to merge study-design and survey-planning (because those who design study will also be in charge of planning).Bernoulli
C
0

You can start with microservices that share a monolithic data source, but only use partial domain entities and value objects

Cade answered 23/7, 2021 at 18:24 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.