Skip to content

Instantly share code, notes, and snippets.

@rasheedamir
Last active January 25, 2022 12:09
Show Gist options
  • Star 15 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save rasheedamir/2f18c6a931fa65343f1d to your computer and use it in GitHub Desktop.
Save rasheedamir/2f18c6a931fa65343f1d to your computer and use it in GitHub Desktop.
DDD, CQRS & ES!

To implement command processing we need the following pieces:

  • Commands which request that something should happen, i.e. some state change
  • Events which indicate that something has happened
  • Aggregates that handles Commands and generates Events based on the current state
  • Event store which stores all events that has happened
  • Application services that receives Commands and routes it to the appropriate aggregate

https://github.com/rasheedamir/event-sourcing-in-practice

  • When designing events, consider making an abstract event type that defines “what” has changed. For example, an abstract AddressChangedEvent. We make it abstract, because we never want an instance of this type. This abstract then has a subclass for each intent of the change, representing the “why” of the state change. For example, AddressCorrectionEvent and CustomerMovedEvent. Since they both extend the abstract AddressChangedEvent, the both contain information about the old and new address.

Hexagonal or Ports and Adapters (Architecture)

  • Design the Application Inside per Functional Requirements: When using Hexagonal, we design the application with our use cases in mind, not the number of supported clients. Any number and type of clients may request through various Ports, but each Adapter delegates to the application using the same API. (from VV)
  • Each client type has its own Adapter, which transforms input protocols into input that is compatible with the application’s API—the inside. Each of the hexagon’s sides represents a different kind of Port, for either input or output. Three of the clients’ requests arrive via the same kind of input Port (Adapters A, B, and C), and one uses a different kind of Port (Adapter D). Perhaps the three use HTTP (browser, REST, SOAP, and so on) and the one uses AMQP (for example, RabbitMQ). There is not a strict definition of what a Port means, making it a flexible concept. In whatever way Ports are partitioned, client requests arrive and the respective Adapter transforms their input. It then invokes an operation on the application or sends the application an event. Control is thus transferred to the inside. (from VV)
  • The application receives requests by way of its public API. The application boundary, or inner hexagon, is also the use case (or user story) boundary. In other words, we should create use cases based on application functional requirements, not on the number of diverse clients or output mechanisms. When the application receives a request via its API, it uses the domain model to fulfill all requests involving the execution of business logic. Thus, the application’s API is published as a set of Application Services. (from VV)

DDD

Aggregate Root

  • Just because Customer references Order doesn't necessarily mean Order falls within the Customer aggregate root. The customer's addresses might, but the orders can be independent (for example, you might have a service that processes all new orders no matter who the customer is. Having to go Customer->Orders makes no sense in this scenario). -- From a domain point of view, you can even question the validity of those references (Customer has reference to a list of Orders). How often will you actually need all orders for a customer? In some systems it makes sense, but in others, one customer might make many orders. Chances are you want orders for a customer between a date range, or orders for a customer that aren't processed yet, or orders which have not been paid, and so on. The scenario in which you'll need all of them might be relatively uncommon. However, it's much more likely that when dealing with an Order, you will want the customer information. So in code, Order.Customer.Name is useful, but Customer.Orders[0].LineItem.SKU - probably not so useful. Of course, that totally depends on your business domain.

  • An AGGREGATE is a cluster of associated objects that we treat as a unit for the purpose of data changes.

  • The first way to tell if your aggregate is right, is that you can look at each of the entities within it and ask "Does this need to be directly accessed?" If you answer yes, then that entity is likely not a part of the aggregate.

  • Personally, I find value in identifying all of the items in the domain on paper (or whiteboard). I will go through a discovery phase with the stakeholder and just get them out there. Then, use these words as leaders in the conversation, trying to understand how they relate. If you interview the stakeholder well enough, the description he/she gives will actually define most of what you are looking for.

  • Another area where there can be confusion is in distinguishing entities from aggregates. Every aggregate has an entity acting as its aggregate root, and for lots and lots of entities the aggregate will consist of just this entity (the "trivial" case, as mathematicians would say). But I’ve seen developers fall into thinking that the whole world must reside within a single aggregate. So, for example, Order contains OrderItems (so far so good) which reference Products, and so the developer concludes that Products are also in the aggregate (no!) Even worse, a developer will observe that Customer have Orders, and so think this means we must have mega-aggregate of Customer / Order / OrderItem / Product (no, no, no!). The point is that "Customer have Orders" does not mean imply aggregation; Customer, Order and Product are all aggregate roots.

  • An aggregate root (sometimes abbreviated to AR) is an entity that composes other entities (as well as its own values) by composition. That is, aggregated entities are referenced only by the root (perhaps transitively), and may not be (permanently) referenced by any objects outside the aggregate. Put another way, if an entity has a reference to another entity, then the referenced entity must either be within the same aggregate, or be the root of some other aggregate.

  • Another DDD principle is that an aggregate root is responsible for ensuring that the aggregated entities are always in a valid state. For example, an Order (root) might contain a collection of OrderItems (aggregated). There could be a rule that any OrderItem cannot be updated once the Order has been shipped. Or, if two OrderItems refer to the same Product and with the same shipping requirements, then they are merged into the same OrderItem. Or, an Order’s derived totalPrice attribute should be the sum of the prices of the OrderItems. Maintaining these invariants is the responsibility of the root.

  • Design Aggregrate Root Properly

  • Order (AR) => Line(Entity) => [Product(AR)] -- In principle this is true. However, your reference to Product is best accomplished by id only (inferred association), and using a copy of other parts such as description and price. Otherwise a change in Product details will change the Line and thus the Order, which may violate the aggregate boundary. Imagine that Product price changes, which ripples to violate a maximum Order total limit per Customer. Further, it is possible and even probable that Product is in a separate bounded context from Order. In that case it makes no sense to directly reference Product even if there was no danger of violating aggregate boundaries.

  • Aggregate boundaries are about consistency of invariants among clustered objects. Bounded contexts are about linguistic consistency, where a term in a model always means the same thing because the context is explicitly bounded.

UBIQUITOUS LANGUAGE

  • To communicate effectively, the code must be based on the same language used to write the requirements—the same language that the developers speak with each other and with domain experts.

Bounded Context & Context Map

  • Context Map
  • A Bounded Context can be considered as a miniature application, containing it’s own Domain, own code and persistence mechanisms. Within a Bounded Context, there should be logical consistency, each Bounded Context should be independent of any other Bounded Context. Communication to and from a Bounded Context is done via a Context Map
  • In an ideal world, each Subdomain maps directly to one Bounded Context.

Modules

  • A module, remember, is akin to a Java package or .NET namespace. We want the dependency between two modules to be acyclic, for sure, but if we do decide that (say) customer depends on order then there’s nothing in particular extra we need to do: Customer can simply import the Order package/namespace and uses its interfaces and classes as needed.

  • Modules (packages or namespaces) meanwhile are key to ensuring that the domain model remains decoupled, and does not descend into one big ball of mud. In his book Evans talks about conceptual contours, an elegant phrase to describe how to separate out the main areas of concern of the domain. Modules are the main way in which this separation is realized, along with interfaces to ensure that module dependencies are strictly acyclic. We use techniques such as Uncle "Bob" Martin’s dependency inversion principle to ensure that the dependencies are strictly one-way.

Factories

Application Services

  • Application services usually handle cross-cutting concerns such as transactions and security. They may also mediate with the presentation layer, by: unmarshalling the inbound request; using a domain service (repository or factory) to obtain a reference to the aggregate root being interacted with; invoking the appropriate operation on that aggregate root; and marshalling the results back to the presentation layer.

Domain Events

  • Some things clearly have identity but no life-cycle, or an extremely limited life-cycle with just one state. We call these things Domain Events and they can be viewed as hybrid of Entities and Value Objects. In the sample application HandlingEvent is a Domain Event that represent a real-life event such as a Cargo being loaded or unloaded, customs cleared etc. They carry both a completion time and a registration time. The completion time is the time when the event occurred and the registration time is the time when the event was received by the system. The HandlingEvent id is composed of the cargo, voyage, completion time, location and type (LOAD, UNLOAD etc).

  • As explained in Domain Events, these are not just paper-thin technical notifications. They explicitly model business process activity occurrences that are useful for domain-wide subscribers to know about, and they pack unique identity and as many knowledge-conveying properties as necessary to clearly get their point across.

  • Domain Events Salvation

  • Be Careful about What the Event Handler Does: Remember, the Application Service controls the transaction. Don’t use the Event notification to modify a second Aggregate instance. That breaks a rule of thumb to modify one Aggregate instance per transaction.

  • The examples in that pattern and my use of it are lightweight because there is no network involved in subscribing to Events and publishing them. All registered subscribers execute in the same process space with the publisher and run on the same thread. When an Event is published, each subscriber is notified synchronously, one by one. This also implies that all subscribers are running within the same transaction, perhaps controlled by an Application Service that is the direct client of the domain model.

  • It may be necessary to provide additional state and behavior if subscribers require more than the indication of the Event’s cause. This could be conveyed by enriched state (more properties) or operations that derive richer state. Subscribers thus avoid querying back on the Aggregate from which the Event was published, which could be needlessly difficult or expensive.

Vidoes

MicroServices

Software Architecture

  • Quantify the un-quantifiable: Tom Gilb at TEDxTrondheim

  • Software Architecture vs Code

  • Architecture, regardless of the domain, is about structure and vision.

  • The code tells a story, but it doesn’t tell the whole story!

  • Architecture is about the stuff that’s hard or costly to change. It’s about the big or “significant” decisions, the sort of decisions that you can’t easily refactor in an afternoon. This includes, for example, the core technology choices, the overall high-level structure (the big picture) and an understanding of how you’re going to solve any complex/risky/significant problems.

  • Big up front design typically covers these architectural concerns but it also tends to go much further, often unnecessarily so. The trick here is to differentiate what’s important from what’s not. Defining a high-level structure to put a vision in place is important. Drawing a countless number of detailed class diagrams before writing the code most likely isn’t. Understanding how you’re going to solve a tricky performance requirement is important, understanding the length of every database column most likely isn’t.

  • "The conclusion drawn is that agility is relative and time-based. If your software team can’t deliver software and keep pace with changes in the environment, your team is not agile. If you’re working in a large, slow moving organisation that rarely changes, you can probably take months to deliver software and still be considered “agile” by the organisation. In a lean startup, that’s likely to not be the case."

  • Simple Sketches for Diagramming your Software Architecture

  • Software Architecture for Developers

  • My definition of Software Architect role:

    • Architectural Drivers: Understanding the goals; capturing, refining, and challenging the requirements and constraints.
    • Designing Software: Creating the technical strategy, vision and roadmap
    • Technical Risks: Identifying, mitigating, and owning the technical risks to ensure that the architecture "works"
    • Architecture Evolution: Continuous technical leadership and ownership of the architecture throughout the software delivery.
    • Coding: Involvement in the hands-on elements of the software delivery
    • Quality Assurance: Introduction and adherence to standards, guidelines, principles, etc.
    • Mentoring/Coaching: Mentoring and coaching the team members to strengthen their technical and non-technical skills.
    • Collaboration: Enabling collaboration between stakeholders of the project.
  • a software system is made up of a number of containers, which themselves are made up of a number of components, which in turn are implemented by one or more classes.

    • Context: A high-level diagram that sets the scene; including key system dependencies and actors.
    • Container: A container diagram shows the high-level technology choices, how responsibilities are distributed across them and how the containers communicate.
    • Component: For each container, a component diagram lets you see the key logical components and their relationships.
    • Classes: This is an optional level of detail and I will draw a small number of high-level UML class diagrams if I want to explain how a particular pattern or component will be (or has been) implemented. The factors that prompt me to draw class diagrams for parts of the software system include the complexity of the software plus the size and experience of the team. Any UML diagrams that I do draw tend to be sketches rather than comprehensive models.
  • Sketching API Connections

  • Technology Stack Recipes Java & Grails

  • Risk Storming

  • Software Architect responsibility is to indentify and document following:

    • Functional Requirements
    • Non-functional Requirements
    • Constraints
    • Principles

Agile

  • User Story form As a < role > I can < activity > so that < business value >

    • Role - represents who is performing the action. It should be a single person, not a department. It may be a system if that is what is initiating the activity.
    • Activity – represents the action to be performed by the system.
    • Business Value – represents the value to the business.
  • INVEST guideline:

    • I - Independent The user story should be self contained, in a way that there is no inherent dependency on another user story.
    • N - Negotiable User stories, up until they are part of a Sprint, can always be changed and rewritten.
    • V - Valuable A user story must deliver value to the end user.
    • E - Estimable You must always be able to estimate the size of a user story.
    • S - Sized appropriately User stories should not be so big as to become impossible to plan/task/prioritize with a certain level of certainty.
    • T - Testable The user story or its related description must provide the necessary information to make test development possible.
  • SMART:

    • S – Specific The task is specific enough that everyone can understand what's involved and prevents overlapping.
    • M – Measurable The team can measure that the task is Done. This requires the team to have a clear definition of Done.
    • A – Achievable The task is achievable by whoever from the team takes on this work.
    • R – Relevant Every task should be relevant by contributing directly to a story and each task can be explained/justified.
    • T – Time-boxed A task should be time-boxed by setting the right expectation for how long it should take to complete the task.
  • US (User Story) vs UC (Use Case) vs FR (Functional Requirement)

  • Why, What & How?

Sample Implementations!

DevOps!

  • Have skills of both "development" & "operations".

Quality Assurance

SAGA

  • A Saga is a special type of Event Listener: one that manages a business transaction. Some transactions could be running for days or even weeks, while others are completed within a few milliseconds. Each instance of a Saga is responsible for managing a single business transaction. That means a Saga maintains state necessary to manage that transaction, continuing it or taking compensating actions to roll back any actions already taken. Typically, and contrary to regular Event Listeners, a Saga has a starting point and an end, both triggered by Events. While the starting point of a Saga is usually very clear, there could be many ways for a Saga to end.

  • While ACID transactions are not necessary or even impossible in some cases, some form of transaction management is still required. Typically, these transactions are referred to as BASE transactions: Basic Availability, Soft state, Eventual consistency. Contrary to ACID, BASE transactions cannot be easily rolled back. To roll back, compensating actions need to be taken to revert anything that has occurred as part of the transaction. In the money transfer example, a failure at Bank B to deposit the money, will refund the money in Bank A. In CQRS, Sagas are responsible for managing these BASE transactions. They respond on Events produced by Commands and may produce new commands, invoke external applications, etc. In the context of Domain Driven Design, it is not uncommon for Sagas to be used as coordination mechanism between several bounded contexts.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment