< Back to GuruStop
Written By Mohamed Meligy (@Meligy)

My Notes From Vaughn Vernon's IDDD Workshop

Day 1

DDD-Lite, what's that?

Tactical patterns, not bounded context

Vaughn Vernon's Github Profile

"Intention Revealing Interfaces"

Functional is booming

Hyperbolic Discounting

Implementing CRUD first, what happens next?

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'"

`SomeCustomerInterface.updateCustomerAddress(ValueObject:addressDetails)`

"Tell, Don't Ask"

"Linguistic Models"??

SaasOvation

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

"Shared Kernel"

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"

"Hexagonal Architecture"

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".

Day 2

Entities

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?).

Favor SetCollaborator(something) vs. Collaborator = something; in .NET, because "Set Collaborator" is what the ubiquitous language uses.

Unique Identity

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 ICalculateBusinessPriority and IPlanBacklogItems.

Value Objects

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 enum.

Structs are not good for ValueTypes (surprisingly?), because they are mutable.

Gojko Adzic

Aggregates

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.

Assume 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.

You can use ID ValueObject instead of navigation property, from child to parent, like an Issue can have a PdoductId instead of Product.

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.

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).

Let's say you have a large field, like UseCaseDefinition, you may want to lazy-load this, or create a small aggregate for it.

Domain Services

"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.

Domain Events

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.

Persisting Events (Side Discussion)

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.

Event-Driven Modeling

White-board Exercise:

  1. 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.

  2. In front of each event card, add another colored card pointing the command that raises it.

  3. 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).

Day 3

Modules

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.

Factories

Interesting naming for factory methods: tenant.OfferRegisterationInvitation(), product.scheduleRelease(), product.planBacklogItem(), etc. Note the focus on business meaning / ubiquitous language.

From workshop exercise, a new term Law of Demeter.

Persistence

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.

Shows an example of catching DB exceptions in the repository, and returning an application-specific (or persistence agnostic, whatever) IllegalStateException.

If you use type hierarchy (inheritance, like Defect inheriting 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 IssueId, etc.

Event Persistence (And Event Sourcing)

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.

Bounded Contexts Integration

OK, we agree no flat files or DB integration, will take that as a given. This is messages / RPC talk.

RESTful Messaging

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).

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.

Messaging / Published Language

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 in Events / Tracking Event Sequence

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.

Sending Commands As Messages (not just events)

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).

Timeouts / Retries / Idempotency

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...).

Application Layer / UI

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.

Projections

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).

CQRS

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.

Q & A