secure by design - Core concepts of DDD
Abstract
- Building domain models is a good way to promote deep learning about the domain.
- A domain model should be a strict and unambiguous representation of the domain that captures only the most important aspects.
- When creating a domain model, you make a choice among many possible models.
- The domain model forms a language for communicating about the system.
- Entities, value objects, and aggregates are the basic building blocks for your domain model.
- Entities have an identity that’s consistent during their life cycle and can contain other entities or value objects.
- The uniqueness of entities always has a scope, and that scope depends on your model.
- A value object doesn’t have an identity but rather is defined by its value.
- A value object must always be immutable and should form a conceptual whole.
- An aggregate is a conceptual boundary that groups together other model objects and is responsible for upholding invariants among those objects.
- An aggregate always has an aggregate root and, in code, that root is typically the same as the aggregate.
- The aggregate root has global identity because this is the only part of the aggregate that other parts of the model can hold a reference to.
- The ubiquitous language is spoken by everyone on the team, including domain experts, to ensure a common understanding.
- The domain model is bound by the semantics of the ubiquitous language. The bounded context is the context in which the semantics of the model hold. As soon as the semantics change, the model breaks and the boundary of the context is found.
- Using Conway Law is a good starting point when trying to find the boundary of a context.
- Data crossing a semantic boundary is of special interest from a security perspective because this is where the meaning of a concept could implicitly change.
- focus on the core domain
- explore models in a creative collaboration of domain practitioners and software practitioners
- speak a ubiquitous language within an explicitly bounded context
Models are tools for deeper insight
If you fail to master the complexity of various technical aspects, you get a system that’s less useful. But, if you fail to master the complexity of the domain, you get a system that’s doing the wrong thing. In that regard, the domain is the critical complexity.
For a domain model to be effective, it needs to:
- Be simple so you focus on the essentials
- Be strict so it can be a foundation for writing code
- Capture deep understanding to make the system truly useful and helpful
- Be the best choice from a pragmatic viewpoint
- Provide you with a language you can use when you talk about the system
Note
Class diagrams in particular are often confused with being the model, but the model, as such, isn’t any of the representations. The model is the conceptual understanding of what you consider as essential in your modeling. The main benefit of keeping models as really simplified versions of reality is that simple models are easier to make strict.
Some terminology
- Domain: A part of the real world where stuff happens (for example, the domain of baggage handling)
- Domain model: A distilled version of the domain where each concept has a specific meaning
- Code: An encoded version of the domain model written in a programming language
Models must be stricts. A system that is too lenient is prone to mistake, and such leniency can result in security flaws.
Building blocks for your model
Entities
Entities are one type of model object that have some distinct properties. What makes an entity special is that:
- It has an identity that defines it and makes it distinguishable from others.
- It has an identity that’s consistent during its life cycle.
- It can contain other objects, such as other entities or value objects.
- It’s responsible for the coordination of operations on the objects it owns.
⇒ two entities are the same by comparing their identities, instead of their attributes
Some examples of what is an entity: a car, a customer, …
The identity and uniqueness of an entity is determined by its identifier. The identifier can be a generated unique ID, and sometimes it ca e the result of applying some function to a selected set of attributes of the entity. In the latter case, you need to pay careful attention to no include any attributes that can change over time.
As a rule of thumb, favor generated IDs over an identity based on attributes.
Value objects
- it has no identity that defines it, but rather it’s defined by its value
- it’s immutable
- it should form a conceptual while
- it can reference entities
- it explicitly defines and enforces important constraints
- it can be used as an attribute of entities and other value objects
- it can be short-lived
A value object is not just a data structure that holds values. It can encapsulate logic associated with the concept it represents, e.g. a GPC could have a method that calculates the distance between itself and another GPS point using nontrivial numerical calculations.
Value objects should enforce their own invariants, e.g. enforce the typical range of a person’s age.
These types of invariants should be enforced within the value object itself and not be put into other domain objects or utility methods.
Aggregates
- Every aggregate has a boundary and a root.
- The root is a single, specific entity contained in the aggregate.
- The root is the only member of the aggregate that objects outside the boundary can hold references to. Thus:
- The root has global identity.
- The root controls all access to the objects within the boundary.
- Entities other than the root have local identity. Their identities don’t have to be known outside of the aggregate.
- The root can pass references to internal entities to other objects, but those references can only be used transiently and can never be held onto.
- The root can pass references of value objects to other objects.
- Invariants between the members of the aggregate are always enforced within each transaction.
- Invariants that span multiple aggregates can’t be expected to be consistent all the time, but they can eventually become consistent.
- Objects within the aggregate can hold references to other aggregates.
E.g. a company with employees that can only be identified within the company (local identifier). This means that the company, which is globally identifiable, can be referenced and looked up by others, but the only way to get to an employee is to go through the aggregate root, the company. The same goes for assigning new roles to employees. The role assignment is handled by a method on the company. Because all operations on the aggregate are controlled by the root, it becomes a straightforward task to uphold the invariants regarding the employees.
Bounded contexts
Ubiquitous language
Existing or being everywhere at the same time.
Transclude of secure_by_design-ubiquitous_language.excalidraw