< Back to GuruStop
Written By Mohamed Meligy (@Meligy)
DDD-Lite, what's that?
Tactical patterns, not bounded context
Vaughn Vernon's Github Profile
"Intention Revealing Interfaces"
Functional is booming
Implementing CRUD first, what happens next?
Said to be "less Agile" if you use it now saying you'll rewrite if/when required
Use "Score Boards" to decide (from the slides)
Business operation: That's use case flows or user stories.
Using DDD in brown-field projects, encapsulate instead of rewrite.
The gap between two minds, often handled by business analysts.
Quorum of team members
It should be common that words in ubiquitous language are later used in marketing.
"PO and Team decide what stories go into sprint", hard to put into a model.
"This is a Candidate for discussion" a cover for proposing something as a consultant that your client may think it will not work at all.
"some think procedural and functional are the same, they're not. Because you write functions in C doesn't mean it's functional" -- note from future me: he was probably referring to procedural, not functional.
"Don't solve any problem using CRUD words, don't use 'CreateOrUpdate'"
You have a repository, you get the customer, then you call the method on it
Except for factory method, which does create the customer
"Tell, Don't Ask"
How do you define a module? That's another overloaded term
Each sub-domain sees itself as a primary domain, and other sub-domains a secondary sub-domains it interacts with
Strive to make a 1:1 Sub-domain : bounded-context
Complexity might be a reason to have another sub-domain in the same bounded-context
Another reason is being a shared sub-domain
If it needs another UI, DB, etc., maybe it needs its own bounded-context
All sub-domains need to know about the core-domain. Some sub-domains need to know about some other sub-domains, and not about some other sub-domains
Market Trade: You may end up buying at different price if it changes just before you perform the order
One definition of Partnership: They succeed or fail together
Like a shared sub-domain (but there was a note about not including behavior)
Shared tests that pass (In CI, by all teams)
In Customer-Supplier model (vs. Partnership), if you can't negotiate with the other party, you are a "Conformist".
Anticorruption Layer: is (unlike what you may think) a translator between different sub-domains
You see fields and setters like:
authorName authorUsername authorEmailAddress
"Event Driven Modeling"
Application services control transactions, in the sense that a transaction is the steps/flow we often put in controllers. You don't want the domain service to do that (this should sound surprising to me).
CQRS: Greg Young: 1 Interface (Get, Do) -> 2 Interfaces (SomethingQueryService, SomethingCommandService)
The faces in the workbook represent "Actors" from the "Actor Model".
An actor queues messages, never processes more than one message at a time (yes, it's synchronous)
Aggregates can work as aggregates. Controllers act as adapters (refer to workbook for adapters)
Entities are the models you care about their individuality (hence the ID).
Entities Can be immutable!
If you see 2 or more "set" operations in a row
entity.Prop = something;, you are missing a concept (a ValueObject maybe?).
Collaborator = something; in .NET, because "Set Collaborator" is what the ubiquitous language uses.
Cab be generated by either a persistence store or another bounded-context, both are reliable.
Order of Id can matter, say for domain events etc.
Id can be generated from Repository
Constructors are used in Entities to protect against invariants.
Tenant ID might be part of Entity ID (which is how SaasOvation is like).
Use "Role Interfaces".
IProduct might have
There is a point that these are very functional way of programming.
They can be standard types as well, like
IssueType that is just an
Structs are not good for ValueTypes (surprisingly?), because they are mutable.
In the simplest form, aggregates are entities, even if they don't aggregate other entities.
Aggregates encapsulate a transaction. Anything that doesn't belong to the transaction is not in the aggregate, not just model navigation properties (this must be consistent with that).
You don't have to use aggregates for DDD.
Challenge the word has (like "product has backlog items"). You'll be tempted to add a collection to the
Product class, but maybe there's no use of doing that.
Release aggregate with
Issues collection. Two calls to add a new
Issue will fail.
Yes, we agree, they shouldn't. The
Release entity didn't change.
This means the aggregate boundaries are wrong in the first place.
Issue is not supposed to be contained in this aggregate.
lated term to check is "Transaction Foils".
You can use ID ValueObject instead of navigation property, from child to parent, like an
can have a
PdoductId instead of
From DDD book: Any rule that spans aggregates will not be expected to be up-to-date at all times.
Note the business decides what time span is allowed for this Eventual Consistency to happen.
Use Domain Events for other entities that will be affected by the transaction.
Don't even put unrelated properties in the same entity.
If other changes are the job of the user executing the change, use Transactional Consistency, otherwise, allow Eventual Consistency (via Domain Events).
A book to consider: Programming Pearls (Second Edition).
Maybe you have different aggregates that are related ( but not changed in the same transaction, so yes, are valid as transaction) like changing hours on a Task, that affect the status of a whole WorkItem/Issue/Story (0 -> Done, else Committed).
In the UI, people will expect seeing the status change when they see hours.
That's an exercise for the dev to solve, but generally ask the question: Whose job it is?, as in, the user or the workflow...
Let's say you have a large field, like UseCaseDefinition, you may want to lazy-load this, or create a small aggregate for it.
"Double-dispatch": That's a term for the domain service calling a model that in fact calls the same domain service again.
Often lightweight, unless used as an anticorruption layer.
The domain service operations (methods) are stateless, although the domain service itself can hold state.
Domain services should not control transactions, because you may compose multiple domain services in one transaction.
Needed if there's a method in a model, that you feel you can make static (because it doesn't depend on the state of the model, as in: any properties in it), you create it as a domain service instead.
The difference from application service is domain services contain business logic. Application services only call domain services for logic, don't perform it themselves.
Triggering phrase: You often use them when you see things like "if / when something happens, something else needs to happen". Another obvious phrase is "notify some other party or system when some change or action happens".
Should be helpful for batching (scheduled batch processes) too.
If you are storing events (as well as the data), if there is a bug in calculating the data, you can delete the data and replay the events again.
Seems using events is nice way to make calculated values just semi-static data (not accurate term or so), that is updated by the events that react to the change of calculation dependencies.
The way Vaughn recommends for publishing events is writing them to DB. The idea is that they're written in the same transaction as the domain changes they react to, so the transaction either fails or passes as a whole (that's another reason he doesn't recommend using MongoDB for it).
Then you can have a background process that reads the events from tables and publishes them to a message bus like RabbitMQ for example, that event subscribers read from afterwards.
Model (Stick cards for) all the events happening in the system since the first user uses etc to end of all flows. Order them in the time order they happen.
In front of each event card, add another colored card pointing the command that raises it.
On top of each pair of cards from before, add another colored card for the aggregate of that command.
This allows domain experts to join the conversation, and allows writing scenarios.
Mark the bounded-context boundaries (use lines or different colored cards or so).
One module for one or few aggregates that are cohesive.
Don't use namespaces as mechanic way to separate them, use the ubiquitous language.
Make them loosely coupled. Have acyclic (unidirectional) dependencies. Try to relax the rules between child and parent modules.
If you separate services (by namespace, etc.) from model, the caveat is you end up with anemic model, business logic in services and model as data holders.
In .NET, it's a pain to have a module named
Product and a model (class) called
Product as well.
One workaround is using plural names for the module.
If you have multiple classes per aggregate, he seems to keep each aggregate in its own child module. Thinking in folders inside same Visual Studio project, think "Product" folder that has "Issue" folder / sub-module, etc.
Ports and Adapters (as in DDD terms) don't have to be called so in the code. Like using the name "View" instead of "Adapter".
One idea may be separating bounded contexts based on modules. Meaning we start by modules before bounded contexts. You'll end up with modules that are also sub-domains.
Interesting naming for factory methods:
Note the focus on business meaning / ubiquitous language.
From workshop exercise, a new term Law of Demeter.
Repositories tend to be one of two, Collection-Oriented Repository (Eric Evans' original definition), or persistence oriented (has particular needed operations, if I got that right, and is what I often do).
A Collection-Oriented Repository, like the name implies, acts like an ordered Set collection.
You'll often have one repository per aggregate.
It's often OK to insert multiple aggregates in one transaction (not that it's recommended or so), but modifying more than one aggregate is where you may get consistency issues if transactions fail (because each aggregate is its own transaction boundary). Failure to insert thought will just, well.., fail to insert.
Again we get into the conversation that you need a DB that support ACID for persisting domain events and model changes in the same transaction, and the note that MongoDB does not have that, and this makes it quite not possible, you have to allow the possibility of event persistence failing even though model change persistence passing.
There's something to look at for this though, called MongoDB event Store
Which may or may not be this NEventStore thing
Shows an example of catching DB exceptions in the repository, and returning an application-specific
(or persistence agnostic, whatever)
If you use type hierarchy (inheritance, like
Issue), using a generic repository
will be hard, because you'll need to know what type to cast to not just for entity but also for ID
Event Sourcing in DB, events are stored as streams, with versions, where the order of the versions is the order of executing the events. They also have a stream name that is coming from aggregate ID (often concatenating the entity ID and tenant ID). Vaughn seems to suggest using First-In-Wins rule for concurrency here (assuming you have a unique key on stream name + version).
If you can't rely on ordering of fields, like version column order in any RDB, which may happen in Non-RDB like LevelDB, you may want some sort of correlation ID (or whatever), where each event has a field pointing to the previous event GUID, so when selecting for all events after a particular event, you find the events that specify this particular event's ID as a correlation.
An integer type field is suggested (while still storing a GUID / value to correlate to), so that you still get order of events (can be the same as the version I guess). Sequential GUIDs are too much for that.
On Event Sourcing and snapshots, Vaughn suggests it takes thousands of events per aggregate to worry about them. He says for example ebay said they had about 6-8 events per aggregate, so, they never needed snapshots (which is a logic to say probably you never will need them too).
On the subject of implementing event sourcing in Code, this seems to be a good start: AggregateSource, which is a collection of infrastructure classes to get up to speed with event sourcing, which plays nicely with Greg Young's eventStore as well.
OK, we agree no flat files or DB integration, will take that as a given. This is messages / RPC talk.
Interesting to see even in Java world, REST / HTTP is favored over classic RPC.
With distributed stuff though, you get issues like network reliability, latency, bandwidth, security, network topology changes (network admins and their changes, also another challenge for just getting answers), etc.
A Notification Media Type can be used for creating Published Language. Media Type means a REST Media Type (the example shown looks to me like WSDL in SOAP).
For naming Media Type type-name, a suggested way is MyBoundedContextName.MyAggregateType.EventName
Custom REST Media types tend to use versioning in the type name
Vaughn seems OK with using
Media Type and
Published Language (in DDD sense) interchangeably
Eventually you want to move all clients to newest version of your API of course
Vaughn seems to suggest manual mapping when consuming the API instead of generated classes
(generated classes here sound like SOAP proxies, although SOAP was never mentioned here). You have a
ReasourceReader ("Resource", because REST), which reads fields (and converts their types)
in similar way to a .NET DbReader does.
A side note, for Media Types, the HTTP Content Type seems to often be "application/TypeNameAsExplainedBefore+json".
Side Discussion: interesting use of HTTP status code was when calling UserInRole resource, for existing user with no roles, returning HTTP Status Code 204 "No Content", with the thought process that 404 is for when you don't get a user at all. Seems there's an argument against that too, but Meh!
OT - Side Discussion: Consider trying out the Vitamin B (B6 and B12) and D, and maybe C, as powder stuff. This stuff must be good I bet.
That's using events and a message bus (think RabbitMQ for example) instead of REST.
The messages are often events. Each sub-domain / bounded context subscribes to the events it cares about. This may be to update the sub-domain's understanding of data changed in another sub-domain. For example in SaasOvation / Agile PM (Project Management) sample, this may be Identity And Access sub-domain Role changes of a User, that the Project Management sub-domain may subscribe to, to update its cache of users if (and only if) the role changes was one of the roles Agile PM cares about.
In order to be autonomous though, which means depending on Event Sourcing for creating model, the Agile PM will need all events from Identity system just to get a user name or something.
One way to avoid this is to add extra related information inside the events we published, so, when the role changes, we publish an event that also has the user username and name in it, so other publisher get it without having to get all Identity system events and building Identity models, etc.
The filtering of data in the event we subscribe to (e.g. we only need PM and Team Member roles in PM context) can be done in a filter dispatcher class. This is also a port adapter in you are thinking in Hexagonal architecture terms.
Dates matter in events here, say disable event fails, which we tried to disable wrong person, so a correction enable is sent and passes, then we try disable again as we recover failed events. If we have date on it, then we can know it was before enable, and our change tracker can say "but the user was enabled after that", and will not do the wrong disabling.
This means we need to track changes coming from other sob-domains though. To decide whether we need to do so, for every event ask yourself what happens if this event was delivered later than whatever other events that feel related to it.
In several examples, one being creating a new Product (Agile PM Context) and requesting new Discussion (that's a Collaboration domain context), the message sent like ProductCreated has a Command message that is requesting a new Discussion. This seems to be just fine, even though it means Agile PM knows a bit about Collaboration context (think another software project).
One topic related to their interaction too is that they live in the same Composite UI (as in DDD term).
We should protect against repeating actions due to receiving the very same event multiple times, like when we get multiple discussion creation command messages, we check if we have a product discussion already. The check can be in an application service (because it's like task management, a bit, where checking and creation are domain services).
This style is preferred to the change tracker and date style in enabling/disabling user scenario, because we don't request any data (the date in that enabling/disabling example) that leaks the application is doing (comparing the date to decide whether to enable/disable).
Tiemouts can be handled via process trackers. Not much to be said in here (maybe because you often use messaging-products for? anyway...).
A typical UI needs info from multiple aggregates. One way to allow this is using a RenderDTO created directly talking to from Aggregate instances. This means the aggregates need navigation properties and exposing its details. Not very good.
To hide this, maybe we can handle the informing of properties using a Mediator (think SW patterns). That's a class that handles talking to the aggregates and generating the DTO.
Domain Payload Object is another way. There's another word State Presentation that he mentioned is close to View Model (think MVVM), it seems to be essentially the same as saying Presentation Model / View / Presentation.
Going through the standard IDDD sample code Vaughn uses (available on Github, maybe should check it even though Java) shows the use of projections, a projection per use (thinking per view, if I got that right, which is reassuring).
The projections were simply calling DB queries directly and mapping to projection objections. There were extra field returned sometimes, the fields you need to be able to issue commands later. I assume these are things like IDs and stuff, not sure what else (if any).
Vaughn points CQRS doesn't require eventual consistency. It allows you to begin with transactional consistency and move to eventual consistency later if performance requires it.
Commands are typically batched in a single lifecycle. All commands executed in the same lifecycle are batched together. A lifecycle starts a unit of work, and sets listeners for domain events so that when the Unit Of Work (think: transaction) is committed, they are persisted with the changes from commands.
What to do when a particular listener to an event may need some specific data in one case? I hate that I have to check for this case and raise different event etc
Would we really use HTTP Status 204 for UserInRole when user exists but has no role?