Notes from the book Rest In Practice. There's plenty more in the chapter that is not mentioned here, this is just the stuff I found useful/interesting and worth putting down.
Safe and Idempotent
- Safe means the action carries no side effects. GET is safe.
- Idempotent means that the outcome is the same no matter how many times you call the action. GET, PUT, & DELETE are all idempotent.
Response Codes
- 201 Created - when the action created a request. Should respond with a Location header with the address of the new resource.
- 204 No Content - when the action completed successfully and the client should expect no response body.
- 400 Bad Request - when the request did not meet requirements, or provide all necessary information required to complete the action.
- 405 Method Not Allowed - when the URI template does not support the method, irrespective of the status of the resource it represents. An Allow response header should be returned that specifies a comma separated list of methods that are permitted.
- 409 Conflict - when the method performed on a URI is at odds with the current status of the resource. E.g, trying to PUT on a URI representing a resource that is deleted.
- 412 Precondition Failed - when the attempted action fails an If-Match or similar header. See Conditional Request Headers below.
POST vs PUT vs PATCH
- When you POST, you are asking the server to create you a resource. The response should contain a Location header with the URI of the object created.
- PUTting on a URI instructs the server to update the entire resource with the representation supplied in the request body. i.e. to overwrite the resource entirely.
- PATCHing is asking to overwrite a specific part of the resource, so the request body is smaller and the rest of the existing resource remains as is.
Entity Tags
An ETag response header is used to identify the version of the resource. Typically, a hash of the response body of a GET request of a resource is used as it is a shorter, unique representation of the entire state of the resource at that moment in time. A version number or date time stamp could also be used.
Conditional Request Headers
An If-Match header can be provided with a PUT request (for example) as a way of telling the server to only perform the associated action if the client is operating on the latest version of the resource. If the value of the ETag returned with a GET request is used as the If-Match header of a PUT request, we are telling the server to make sure that the current ETag of the resource matches the If-Match value the client provided before carrying out the operation.
Should the ETag not match, then the server should return a 412 Precondition Failed response code. This implies that the client should perform a GET request to obtain the latest ETag of the resource, so that the client is aware of the latest version of the resource before making changes. This tries to enforce changes being overwritten by sending change requests from an out-of-date client.
Also supported is an If-None-Match request header which should also act on the ETag value. If-Match and If-None-Match should also support Wildcard (*) character matching. Additionally, If-Modified-Since and If-Unmodified-Since since request headers are available, to be compared to the value of the Last-Modified response header. These operate on date-time values and are specific to the second.
Part III Refactoring Toward Deeper Insight
8 - Breakthrough
- Breakthroughs cannot be forced, the usually occur after many modest refactorings.
- Concentrate on knowledge crunching and cultivating robust Ubiquitous Language.
- Watch for opportunities to gain deeper insight and do not hesitate to make even minor improvements.
- Breakthroughs can lead to a cascade of more breakthroughs.
9 - Making Implicit Concepts Explicit
- Developers need to sensitize to the hints revealing implicit concepts and sometimes search them out.
- Listen to the language the domain experts use. It is a warning when the experts use vocabulary that is not present in the design.
- Watch for puzzled expressions on domain experts faces when you mention particular phrases.
- When the missing concept is not on the surface of conversations with domain experts, try digging around the most awkward part of the design - i.e. the place that is hardest to explain.
- Actively engage domain experts in the search, play with ideas or use them for validation.
- Usually contradictions from opposing domain experts can reveal deeper insight to the domain.
- Business books on the domain are a great source of information when the domain expert is not available.
- Explicit Constraints, such as invariants, should be extracted in the design so that their intention is more obvious by using descriptive method names within the object.
- Constraints do not belong in the object if they require data that doesn't fit in the object's definition. Or if requirements conversation revolves around the constraint but they are hidden in procedural code within the object.
- The Specification Pattern provides a way of expressing conditional rules explicitly in the model (predicates) whilst keeping the logic in the domain layer.
- Specifications determine if an object satisfies a condition, the logic having probably been extracted from an attribute of the property.
- Specifications work well with repositories by specifying criteria to select against, esp with ORMs.
- Specs defined in terms of interfaces decouple designs and help clear logjams by using prototypes.
10 - Supple Design
- Relieve the client from having to know about the implementation by using Intention Revealing Interfaces.
- Describe purpose and effect but not how it is achieved and always with Ubiquitous language.
- Functions do not affect the state of an object and are therefore 'side-effect-free'.
- Function should be able to be called repeatedly and deeply nested. Functions are predictable and safe.
- Commands cause change of state and should be kept separate from queries (CQRS). Commands should not return domain data - leave that to functions.
- Where possible, return Value Objects from functions so you don't have to maintain its life-cycle. Furthermore, all Value Object operations should be functions as they are immutable and have no state.
- Move complex queries into Value Objects to reduce side effects.
- Make invariant conditions explicit by unit testing and making assertions.
- Search for meaningful units of functionality to make designs flexible and easier to understand.
- Use your intuition about the domain to decompose design elements (classes/aggregates) into cohesive units.
- Changes are more easily applied to Conceptual Contours and they are easier to combine and reuse.
- Standalone classes with zero dependencies other than primitives are easier to understand and reduces mental overload by having extremely low coupling.
- Every dependency is suspect until proven to be fundamental to the concept of the object.
- Prioritise the most complex computations into standalone classes (value objects?) and allow other classes to depend on them.
- Operations who's argument and return value are of the same type are said to be closed under the set of instances of that type. Closed operations do not introduce further dependencies.
- Closed Operations can operate under abstract types.
- Declarative design can be burdened by frameworks and DSLs. Use only lightweight plugins that solve a particular mundane problem well and that leave you to design more accurately.
- The specification pattern can be very useful for declarative design because individual specifications can be combined and chained to produce meaningful sentences using Ubiquitous Language.
- When trying to achieve Supple Design start with identifiable subdomains (conceptual contours?) and tackle problems step by step. It is better to make a big impact on one area, making some part of the design supple, rather than to spread efforts thinly.
- Draw on established formalisms of the domain when you can.
11 - Applying Analysis Patterns
- Analysis Patterns are not technical solutions but guides to help you work out the model.
- Analysis patterns can carry tried and tested experience of implementation and maintenance from a mature project and bring model insights, direction & discussion.
- Analysis Patterns provide cleanly abstracted vocabulary.
- Development can be stimulated by using analysis patterns and results often resemble adapted the form adapted to the circumstances, but sometimes can inspire the development in other directions.
- It is wise not to adjust the basic concept of an analysis pattern so as to keep the well understand terms and phrases consistent in your project. If model definitions change then keep class/method names up to date.
- Analysis patterns focus on the most critical decisions and offer choices. They anticipate downstream consequences that are expensive to discover yourself.
12 - Relating Design Patterns to the Model
- Design patterns present design elements that have solved problems before in purely technical terms. They can be applied to the model because they correspond to general objects that emerge in domains.
- The conventional focus of the strategy pattern focuses on substituting different algorithms. However, its use as a domain pattern focuses on its ability to express a process or policy rule as a concept.
- When a technical design pattern is used in the model we need another motivational layer of meaning to correlate actual business strategy to make the pattern more than just a useful implementation technique.
- A consequence of the strategy pattern is that it can increase the number of objects in the design and requires clients to be aware of the different strategies. This can be mitigated by implementing strategies as stateless objects that are contexts can share.
- The Composite pattern offers the same behaviour at every structural level of an inheritance hierarchy.
- The Flyweight pattern has no correspondence to the domain model, its only value is at the design level when implementing value objects. The Composite pattern differs as it is for conceptual objects composed of conceptual objects of the same type.
- This is what makes Composite pattern a domain, as the pattern applies to both model and implementation.
13 - Refactoring Toward Deeper Insight
- Is a multifaceted process: Live in the Domain > Keep looking at things differently > Maintain an unbroken dialogue with Domain experts.
- Seeking insight into the domain creates a broader context for the process of refactoring.
- Refactoring can begin in many ways - usually as a result of some awkward realisation or difficulty implementing a reasonable requirement, divergence of language, or any source of dissatisfaction.
- Exploration teams can be tasked with thinking about how to improve the model. These teams need to be self organised and small in members meeting for short frequent periods. The meetings should be spaced out over a few days to allow time to digest the new model. Brainstorming sessions should always exercise the Ubiquitous language. The end result is a refinement of the language to be formalised in code.
- Knowledge should be sought from other sources in the domain to gain insight. It is always possible that the domain has already got well defined concepts and useful abstractions.
- Refactoring toward deeper insight both leads to and benefits from a supple design. A supple design communicates it intent and limit mental overload by reducing dependencies and side effects and being fine grained only where it is most critical to users.
- Continuous refactoring has to be considered a best practice and part of the ongoing exploration of subject matter, education of developers and meeting of minds of developers and domain experts. Take any opportunity to refactor that benefits the integrity of the design and consolidates the teams's understanding.
- Development suddenly comes to the brink of a breakthrough and plunges through to a deep model-then again begins a phase of steady refinement.
Part I - Putting the Domain Model to Work
1 - Crunching Knowledge
- Communication with Domain Experts is mandatory to accumulate domain knowledge.
- Initial conversations should revolve around an emerging language based on the model.
- Experimenting with different ideas about the model is important.
- Layers of feedback erode the details of the model, only business people and developers needed.
- Learning has to be continuous as the business changes and domain evolves.
2 - Communication and the Use of Language
- Domain experts and developers need to speak the same language.
- The language of the model should be reflected in the code.
- Using the language will force model weaknesses into the open and reveal gaps to be - plugged.
- Model should be the backbone of the ubiquitous language, change in change means change in model.
- If experts don't understand the model there is something wrong with the model.
- Diagrams are useful but should be temporary and serve only communicate the present.
- Documents are useful if they add to the model.
3 - Binding Model and Implementation
- Model driven design removes the separation of analysis models and design models by having a single model that fits both purposes and makes for a very relevant model.
- If a model does not faithfully express key domain concepts, a new model is needed.
- The model must support the ubiquitous language and start with the domain reflected in code in a very literal way that can be improved as deeper insight is gained. This way the code becomes an expression of the model.
- OOP is a great paradigm for expressing models as it inherently supports objects as opposed to procedural languages.
- It is usually a good idea not to try to hide the inner workings of the system from the user as it prevents the user understanding the model, which is usual of benefit to the user as they can reason about it and make predictions.
- The people involved in modelling need also to be taking part in crafting the code. There are subtle relationships in code carry important information about the model that are difficult to communicate verbally.
Part II - The Building Blocks of a Model-Driven Design
4 - Isolating the Domain
- A layered architecture is the most appropriate when trying to employ DDD.
- The Domain layer is where the model lives.
- A separate domain layer allows a model to evolve to be rich and clear enough to capture business knowledge.
- Keeping it separate from infrastructure or UI concerns is imperative.
- Smart UI designs can be practical but are not sustainable long-term.
5 - A Model Expressed in Software
- Entities require an identity so they can be uniquely referenced throughout their lifetime.
- Entity class definitions should be should be kept focussed on the lifestyle continuity and identity i.e. properties that make the class findable with intrinsic characteristics.
- Add behaviour to entities that is essential to the concept, move non essential attributes into other objects associated with the core entity.
- Value objects have no identity as it is not important to tell them apart.
- Objects without identities are more transient and can allow for better system performance without the burden of being identifiable. Applying identities where not needed also confuses the design.
- In value objects we only care about the attributes of the class and they should always be immutable - the value object should be replaced rather than manipulated, e.g. address.
- Value objects can be shared where it is acceptable in the model and of benefit to system resources but is difficult in distributed systems. Shared value objects must never be mutable.
- Services represent valid ways of capturing behaviour that do not belong to an object.
- It is easy to create a service and not apply to an object when it should be in an object but forcing behaviour into an object in which it doesn't belong can distort the model.
- Service operations should be part of the ubiquitous language.
- Services are stateless and have interfaces defined in terms of other domain elements e.g. a bank transfer service deals with other domain elements - money (value) & accounts (entities).
- Modules should be treated as fully fledged parts of the domain and organise objects.
- Code divided into modules means being divided into logical domain concepts. This helps coherent thinking about few topics at once and helps to reason about the system as a whole in terms of its modules.
- Modules should act as a communication mechanism and support the ubiquitous language by reflecting insight into the domain.
- Mixing paradigms should only be considered when all other options for using the primary paradigm for the module in question have been exhausted.
- One way to ease the friction on heterogeneous models is to use the same language in all paradigms.
- Paradigms that do not allow expressive representation of the domain with ubiquitous language should not be used.
Slowly learning that the struggle should always be to use the tools in the best possible way to fit the model, not to adjust model problems so that they are easily solved with our tools.
What happens when domain experts do not know the model well enough to answer deeper questions of things the business hasn't decided yet?
6 - The Life Cycle of a Domain Object
- Invariants are ways to maintain business rules within an Aggregate
- An aggregate is a cluster of associated objects that we treat as a unit for the purposes of data changes.
- An aggregate route is the one entity (of possibly many) in the aggregate that other entities outside the aggregate can hold reference to.
- That means the other entities inside the aggregate only need identifiers distinguishable inside the aggregate, as nothing outside can access them.
- Aggregates are the only thing in the aggregate that can be accessed directly from the database. A delete operation must remove everything within the boundary.
- Invariants (consistency rules) involve relationships between members of the aggregate and must be enforced by persisting the aggregate as a whole.
- Decouple elements to keep clean distinctions between things inside and outside the aggregate boundary.
- Factories remove the need for object clients to know the inner workings of the client.
- They are not intrinsically part of the model but they are a reasoned necessity of the design.
- A factory should be an atomic operation that creates a valid entire aggregate for an entity with all invariants satisfied.
- Factories can over complicate a simple design but the threshold for choosing a factory method is short.
- Factory parameters should be from a lower design layer or basic objects and abstract types.
- Objects don't need to carry around logic that will never be applied in its active lifetime - so the factory can be a good place host the responsibility of enforcing invariants (especially if they are immutable).
- Factories are also suitable for reconstituting inactive objects, ie loading from the db.
- Infrastructure makes it easy to traverse dbs but developers have to try to keep the model in tact.
- Use a repositories to provide access to aggregates routes. Only ever traverse via the route to access objects internal to the aggregate.
- Repositories help maintain focus on the model by emulating an in-memory collection of all entities of a type.
- Repositories decouple domain design from persistence technology (facade) and allow for easy substitution.
- The specification pattern helps complex querying whilst maintaining the model.
- Keep mappings between objects and tables simple when designing relational dbs by compromising normalisation in favour of tight coupling between model and implementation.
7 - Using the Language: An Extended Example
- An Anti Corruption layer can protect external systems that are not based on the same domain model from bleeding into your design.
- Enterprise Segments can be useful value objects for the design to allow communication between modules and preserve the ubiquitous language.
- The application layer should orchestrate the different elements of the domain but should not contain any business logic.
Should repositories be placed in a data access layer or do they belong inside the conceptual modules along with the entities they are for?
Could (or perhaps, should) there be an object oriented language or framework with these concepts of entities, values, aggregates, factories, repositories, anti-corruption layers as first class elements? We would lose precision but we would have a platform that is built for DDD?