16
Entities

WHAT’S IN THIS CHAPTER?

  • An introduction to the DDD concept of entities
  • Uncovering entities in a problem domain
  • Differentiating entities and value objects
  • Examples of entity implementation fundamentals
  • Suggestions to help produce behavior-rich domain models by creating expressive entities
  • Examples of optional design principles and patterns that can enhance the expressiveness and maintainability of entities

Wrox.com Code Downloads for This Chapter

The wrox.com code downloads for this chapter are found at www.wrox.com/go/domaindrivendesign on the Download Code tab. The code is in the Chapter 16 download and individually named according to the names throughout the chapter.

As you work with domain experts, they often refer to concepts that have an inherent identity in the problem domain. For instance, they may talk about a specific customer or a specific sporting event. These concepts are known as entities, and unlike value objects, it is usually not acceptable for two entities that have similar values to be considered equal.

Finding entities in your domain and modeling them explicitly is important for conceptual and technical reasons. If you understand that a concept is an entity, you can start to probe domain experts for related details, such as its life cycle. Technically, you also want to understand which concepts are entities because distinct trade-offs and considerations apply to their design and implementation, as you will see later in this chapter.

Understanding that entities are primarily concepts with a unique identity is unquestionably the main takeaway from this chapter. Although you will see implementation guidance and modeling patterns based on years of refinement in the Domain-Driven Design (DDD) community, it’s important to be aware that many of these details can change over time with new insights and the emergence of new technologies. So as long as you understand the conceptual role of entities, and you are aware of general implementation considerations, you will always be in a good position to at least get your domain model in the right shape.

Understanding Entities

This section offers a theoretical look into what entities are and how they differ from other types of domain objects.

Domain Concepts with Identity and Continuity

A good opportunity to find entities in the domain you are modeling is to pay attention to how domain experts speak. As an example, imagine you are in discussions with a holiday and travel domain expert. The domain expert may explain to you that travelers choose a hotel they like and then book a holiday there. The domain expert also informs you that it is not acceptable for the traveler to be given a booking at a different hotel—even if it has the same name or has other values that are the same. In this situation, the domain expert is implicitly telling you that hotels are an entity, because their identity and uniqueness are important. Without them, it would not be possible to distinguish one hotel from other hotels, and travelers would not get the booking they expected.

Another piece of information to try to extract from domain experts is the type of identity an entity has. In many cases, identity can be an important domain concept in itself and can be explicitly modeled to improve the expressiveness of your domain model. In the case of a hotel, it might actually have a real-world ID that enables many disparate travel agents and applications to share data about it—like availability and user reviews. On the other hand, a hotel may have no real-world unique identifier, so an application would need to generate an arbitrary one. If it’s not clear, then it’s in your best interests to ask the domain expert(s) if an entity has a unique identity in the problem domain.

Aside from identity, but closely related, is another tell-tale sign that you have an entity: continuity. Domain experts may give you clues like, “The order is accepted,” “The order is confirmed after payment,” and “The order is fulfilled by the courier.” These clues indicate that this “thing” has a life cycle in the domain. And usually, to have a life cycle in the domain, an identity is needed to allow the “thing” to be found and updated at various stages of its life cycle.

Uncovering entities can occur at any time; it’s not necessarily the case that you sit down with domain experts and first identify all the entities in a system up front. Some DDD practitioners start by identifying the events that occur in the domain. And from there they collaborate with domain experts to understand which entities are involved with each event. In general, you should always be on the lookout.

Context-Dependent

Admittedly, it can be challenging to distinguish entities from other types of domain objects, like value objects. As mentioned previously, entities are fundamentally about identity—focusing on the “who” rather than the “what.” But it can still be difficult to ascertain whether a concept is an entity or a value object, especially because entities and value objects are context dependent; a concept that is definitely an entity in one domain could unequivocally be a value object in another domain.

A common DDD example of context dependence is money. In a banking application, a customer might put $100 in her bank account. When she withdraws her $100 at some point in the future, she may receive different bank notes or coins than the ones she deposited. This difference is irrelevant, though, because the identity of the money is not important; the customer only cares about the value of the money. So in this domain, money is without a doubt a value object. But in another domain, perhaps involving the manufacture or traceability of money, the identity of individual notes or coins may actually be an important domain concept. So each piece of money would be an entity with a unique identifier.

Implementing Entities

Most entities have similar characteristics, so there are some fundamental design and implementation considerations that you should be aware of, including assigning identity, applying validation, and delegating behavior. In this section, you will see guidance and examples for each of the fundamental design considerations so that once you have uncovered entities in your domain, you can begin implementing them in your model.

Assigning Identifiers

Sometimes an entity’s identity is determined by a natural domain identity, whereas other times there may be no preexisting natural identifier. With the former, it’s your responsibility to collaborate with domain experts to reveal the natural identity, whereas for the latter, you need to generate an arbitrary identity in your application, potentially with help from your datastore.

Natural Keys

When trying to ascertain what an entity’s identity should be, you should first consider whether the entity already has a unique identifier in the problem domain. These are called natural keys. Here are a few examples:

  • Social security numbers (SSNs)
  • Country name
  • Payroll number
  • National ID number
  • ISBN (for books)

You do need to ensure that a natural key is never going to change. If it does, you may have lots of references in your system pointing to the old identity. At best, it’s a hassle to update all of them. At worst, you may forget to update some references or make mistakes that lead to significant business-level problems.

Once you have identified a natural key, and you’re confident it definitely is a natural key, it’s usually straightforward to assign it to an entity. Most of the time, you just need to add a constructor parameter, as shown in Listing 16-1.

You can see in Listing 16-1 that the Book entity has an ISBN constructor parameter that is set as the identity of the newly created instance. You pass the natural key in, and the entity then assumes that identity.

One issue to be cognizant of when using natural keys is ensuring that your object-relational mapper (ORM) or data access technology is configured to allow them. Some frameworks may override the ID you have given an entity unless you explicitly tell the framework that you are managing the ID yourself. This issue also applies to arbitrarily generated keys, which are covered next.

Arbitrarily Generated IDs

When there is no unique identifier in the problem domain, you need to decide what kind of ID you are going to use and how you will generate it. Common formats include incremental numbers, globally unique identifiers (GUIDs) (aka universally unique identifiers, UUIDs), and strings.

Incremental Numeric Counters

Numbers usually have the smallest footprint, but they inherently pose the challenge of maintaining a global counter of the last assigned ID. Conversely, GUIDs are good because they don’t have this problem. Instead, you just generate a GUID which is automatically guaranteed to be unique. However, it does then take up more storage space when it’s persisted. The additional storage is relatively insignificant, though, so for many applications GUIDs are the default approach. Strings tend to be used for custom ID formats such as hashes, amalgamations of multiple attributes, or even timestamp-based approaches.

Listing 16-2 demonstrates using incremental numbers, whereby a static variable keeps track of the last assigned ID and a factory method ensures that each new entity gets the next sequential number for its ID.

If an application crashes, then a static variable like lastId shown in Listing 16-2 loses its value, meaning that old IDs are likely to be reused when the application restarts. To remedy this, you likely need to persist the counter. But doing so is suboptimal for many use cases because of the performance overhead of reading and updating combined with the additional complexity. Some implementations even require fragile locking techniques in distributed or load-balanced environments. It comes down to your best judgment on a case-by-case basis, but if generating IDs is a complex and fragile part of your system, then perhaps GUIDs are an easy way to make life much easier for your team.

GUIDs/UUIDs

Using GUIDs can be a massive simplification over maintaining a global counter. In many cases, the performance, complexity, and synchronization problems completely go away, as Listing 16-3 shows.

GUIDs are guaranteed to be unique, so no matter where the call to GUID.NewGuid(), it always produces a unique identifier. Therefore, VehicleFactory.CreateVehicle() always creates a Vehicle with a unique ID—even if the same method was invoked at the exact time on multiple servers in a load-balanced environment. This demonstrates why you should often favor GUIDs if you’re generating your own IDs.

Using GUIDs can be especially useful when you have logic in the browser that needs to create an entity and needs to post back to multiple back-end application programming interfaces (APIs). Without an ID, there’s no way for the back-end services to know you are posting information about the same entity to each of them. You can solve this problem by creating a GUID on the client using JavaScript. Figure 16.1 illustrates this process.

images

FIGURE 16.1 Creating a client-side GUID and posting to multiple back-end services.

There was a long discussion on the “DDD/CQRS” (Command Query Responsibility Segregation) mailing list about client-side ID generation. It’s definitely worth a read if you are considering this approach or want to learn more about it (https://groups.google.com/forum/#!msg/dddcqrs/xYfmh2WwHKk/XW7eauXcKkcJ).

Strings

Using strings is an opportunity for you to create your own custom ID format. There are many possible strategies that you can use to create formats. One example is including some of the entity’s state in the ID for diagnostic benefits. Listing 16-4 shows a simplified example of this, in which a HolidayBooking entity’s ID is a string containing the ID of the traveler who booked it, the start and end dates of the booking, and a timestamp of when the booking was confirmed.

GenerateId() in Listing 16-4 is called in the constructor of the HotelBooking entity to generate a unique ID using a custom format. This is a trivial example, but it is still similar to implementations used in real applications.

Similar to natural keys, if you want to include an entity’s values in its ID for diagnostic purposes, the values probably should not change. So it is worth being extra careful and having a good understanding of domain characteristics when creating a custom ID format.

Datastore-Generated IDs

It’s often easy enough and safe enough to delegate ID generation to your datastore. Most databases, ranging from SQL databases like MS SQL Server to document databases like RavenDB, natively support ID generation.

Creating datastore-generated IDs often follows the similar pattern of passing your newly created entity into your chosen data access library. Upon successful completion of the next transaction, your entity then has its ID set. Listing 16-5 contains a test case demonstrating this behavior using NHibernate backed by SQL Server.

In Listing 16-5, two IdTestEntity instances are created without an ID. You can see that the initial two assertions verify this. Then, inside a NHibernate transaction, both entities are saved (which equates to being persisted once the NHibernate transaction is committed). The final two assertions verify that the IDs were indeed set as expected. You can see all the code, including the NHibernate configuration and setup, in this chapter’s sample code.

Pushing Behavior into Value Objects and Domain Services

Keeping entities focused on the responsibility of identity is important because it prevents them from becoming bloated—an easy trap to fall into when they pull together many related behaviors. Achieving this focus requires delegating related behavior to value objects and domain services. You actually saw examples of this in the previous chapter when value objects were combinable, comparable, and self-validating (thus keeping logic out of the entities that use them). Equally, you’ll see in Chapter 17 “Domain Services,” that there are often cases where stateless domain operations that at first appear to belong to entities can actually be encapsulated as domain services.

To demonstrate the benefits of pushing behavior from entities into value objects, Listing 16-6 shows an updated version of the HolidayBooking entity previously introduced in Listing 16-4. This version implements key domain policies using the Ubiquitous Language (UL). First, it ensures that the first night of a holiday precedes the last night. Second, it ensures that the booking meets the minimum duration of three nights. It offloads both of these requirements to the Stay value object.

Listing 16-4 demonstrated the initial HolidayBooking entity that was focused solely on identity. It would have easily been possible to implement the new behavior directly inside that class, instead of in the Stay value object shown in Listing 16-6. However, putting this logic directly inside the entity would reduce expressiveness by mixing in logic related to identity with logic related to stays (the period of time for which the holiday booking applies to).

You might think that Stay is quite a small class and it would be fine for the logic to go directly inside the HolidayBooking. But imagine other aspects of a booking, such as extras like transfers or flights. If you put all of those responsibilities inside the HolidayBooking entity it would obscure and intermingle domain concepts. Any time you add behavior to an entity, it’s worth your while to consider whether you can push it into a value object for enhanced domain clarity.

One aspect of your entities to be conscious of when pushing behavior into value objects is the depth of the object graph. In Listing 16-6, the Stay property is public and so are its FirstNight and LastNight properties. That makes it possible for consumers of the entity to call HotelBooking.Stay.FirstNight. Three levels deep is reasonable in this case, but you should be careful about how much of your object graph to expose, because clients will couple themselves to it. In this scenario, if you wanted to refactor the FirstNight and LastNight properties, it may be difficult because other parts of the system are tightly-coupled to them. On a case-by-case basis you’ll have to trade-off the depth at which your object graph is exposed against how far up the object graph you bring public behavior.

Validating and Enforcing Invariants

In addition to identity, a primary implementation requirement for entities is ensuring they are self-validating and always valid. This is similar to the self-validating nature of value objects, although it’s usually much more context dependent due to entities having a life cycle. As an example, a FlightBooking entity may be allowed to have its DepartureDate modified while it is awaiting confirmation by the airline. Once confirmed, however, the validation rules then preclude changes to the DepartureDate in line with business policy. Listing 16-7 shows an implementation of the FlightBooking entity demonstrating these context-dependent validation rules.

When Reschedule() is invoked with an updated departure date, the FlightBooking entity shown in Listing 16-7 throws a RescheduleRejected exception. But it doesn’t do that if the booking has not yet been confirmed. So this validation is context dependent because it only applies in certain scenarios. You may feel that this contradicts the statement that “entities are always valid.” However, “always valid” really means “always contextually valid” in the case of entities.

You can also see in Listing 16-7 that each constructor argument is validated. This is another important guideline for ensuring that entities are always valid. It will also help to prevent problems spreading through your domain and putting your domain into inconsistent states. This example uses specific exception types for implementing constructor validation, but it’s up to you how you want to implement validation. In general it’s best to be expressive.

Entities are also responsible for enforcing a more fundamental form of validation: invariants. Invariants are facts about an entity. They mandate that the values of certain attributes must fall within a certain range to be an accurate representation of the entity being modeled.

In the context of hotels, to find invariants, you might ask yourself, “What makes a hotel a hotel?” or “What does it mean for a hotel to be a hotel?” In a travel and holidays domain, a hotel represents a building with rooms that travelers can book as the base for their holiday. If a building doesn’t have rooms, there’s no way it can be a hotel. Listing 16-8 shows how the invariant of “being a hotel necessities having rooms” can be enforced.

Enforcing invariants doesn’t have to be complicated, although ideally it should be explicit. This is exemplified in Listing 16-8 with EnforceInvariants(). Before construction of the Hotel begins, this method is fired and validates that the constructor arguments satisfy the fundamental requirements of a hotel. In this case, that means ensuring that the Hotel has at least one room. Upon failure of this condition, an explicit HotelsMustHaveRooms exception is thrown, leaving no doubts about the invariant and no way to bypass it.

As mentioned, validation and invariants are similar in nature and appearance. You can see evidence of this by comparing Listing 16-7 and Listing 16-8. However, the subtle, but significant, difference is that the invariant in Listing 16-8 always applies regardless of context. Subtle differences like this are important, because when you are learning about domains, you need to be able to distinguish contextual validation rules from invariants to accurately understand and model your problem domain.

You will see later in this chapter that it is possible to push validation behavior out of entities using specifications. You will also see that when any entity has a number of states, you might want to consider modeling them explicitly if you are tempted to use the state pattern.

Focusing on Behavior, Not Data

A common opinion that many DDD practitioners share is that entities should be behavior oriented. This means that an entity’s interface should expose expressive methods that communicate domain behaviors instead of exposing state. More generally, this is closely related to the OOP principle of “Tell Don’t Ask.”

Focusing on an entity’s behavior when using DDD is important because it makes your domain model more expressive. Also, by encapsulating an entity’s state, that state can only be operated on by the instance that encapsulates it. This means that any behavior that needs to modify the state has to belong to the entity. This is desirable because it prevents you from putting logic that belongs to an entity in the wrong place.

To implement behavior-focused entities you need to be wary of exposing getters and extremely sensitive to exposing setters (making them public). Exposing setters means there is an increased risk that an entity’s state will be updated by other parts of the system in a way that offers no explanation of why it is being updated—hiding the domain concept or the reason for the update, as the next example demonstrates.

If you were modeling a problem domain that involved tracking the results of soccer matches, you might be tempted to implement a SoccerCupMatch entity similar to Listing 16-9, in which all the entity’s state is exposed. However, you should think carefully about implementing a state-oriented design like this.

If you expose an entity’s state for public access and modification, as the SoccerCupMatch in Listing 16-9 does, you leave open the possibility that behavior belonging to the entity can be located elsewhere in the domain that is less explicit. For instance, soccer has an interesting rule called the away goals rule. Two teams play each other twice in a cup match, once at the home stadium for each team. If the overall score is a draw then the team that scored the most goals at their opponent’s stadium (away goals) is the winner. For example, if the first match was 1–0 to team A at team A’s stadium, and the second match was 2–1 to team B at team B’s stadium, the overall score would be 2–2. However, team A scored 1 away goal and team B scored 0 away goals. So team A would be the winner.

If you look at the revised SoccerCupMatch entity in Listing 16-9, you can see that it only exposes its state—the score. Clients of this entity may see the score as 2–2 and erroneously behave like the match was a draw or that team B won. Potentially, there could be multiple clients of the entity that all implement logic for checking the winner of a match in different ways. Such inconsistencies would lead to strange and incorrect behavior in the domain model. Instead, the SoccerCupMatch entity should encapsulate its state and expose the behavior for calculating a winner as the updated version in Listing 16-10 does.

Even if you didn’t know what soccer was, by looking at the SoccerCupMatch implementation in Listing 16-10, you would be able to infer the meaning of the away goals rule. Also as important, by making the state private and only exposing behavior, clients of SoccerCupMatch can only communicate with instances of SoccerCupMatch using domain terminology as opposed to just mutating state. This is a big step toward creating behavior-rich, expressive domain models.

Even with the best of intentions, sometimes you are faced with big compromises in which you appear to have no choice to expose getters and setters. Later in this chapter, you see how the memento pattern can alleviate the need to expose getters and setters.

Avoiding the “Model the Real-World” Fallacy

Even though capturing precise problem domain behavior is a fundamental practice of DDD, an unfortunate consequence is that people new to DDD tend to model too much behavior. They innocently buy into the fallacy that DDD is about modeling the real world. Subsequently, they try to model many real-world behaviors of an entity that are not actually relevant to the software application being built. This is a fairly common occurrence, and a completely understandable one that arises out of a team’s best intentions. Unfortunately, it is problematic because it increases the number of concepts and complexity in a domain model. It also leads to confusion if there are parts of a codebase that appear to never be used.

Once you understand that your domain model is not intended to be a full-scale model of the actual problem domain, you probably won’t make this mistake, or if you do, you will quickly realize and correct yourself. But there is a similar problem that’s sometimes harder to detect; sometimes entities are shaped based on UI requirements. Often, the best solution is to add any translations or UI-related logic onto a view model or data transfer object (DTO) to avoid polluting an entity’s interface and responsibilities.

It’s a familiar sight for technical concepts to creep into an entity. Common examples include dependency injection attributes, validation attributes, and ORM functionality like lazy loading. Again, to many, these are considered pollutants that decrease the expressiveness of a domain model. It’s often best to avoid them as much as possible. Admittedly, sometimes you really will find yourself in a tough situation in which you have to pragmatically balance out the desire for a clean domain model with the need to get things done in the simplest way possible. In such situations, you may have to grit your teeth and accept the pain of polluting your domain model, but don’t give in without a fight.

Designing for Distribution

In recent years distributed systems have become the new normal. Subsequently, new design choices have arisen for domain models and, in particular, entities. Overwhelmingly, however, most DDD practitioners strongly suggest not distributing entities. In essence, this means that an entity should be confined to a single class in a single domain model inside a single bounded context. This guidance makes more sense if you consider the typical Customer entity shown in Listing 16-11.

Addresses, personal details, order history, payment details, and loyalty are all present on the Customer entity in Listing 16-11. In a monolithic application, there may be some reason to justify this design. But in a distributed system, addresses, orders, payment details, and loyalty information may reside in different bounded contexts. So to load this entity from persistence, queries against multiple databases, in different bounded contexts, might be necessary. As you saw in Chapters 11 through 13, tight coupling between distributed components is highly susceptible to scalability, resiliency, and other problems arising from the fact that there is a network involved.

With distribution in mind, the Customer entity in Listing 16-11 actually represents a number of different concepts that live in different bounded contexts. Listing 16-12 illustrates how the loyalty, orders, and payments aspects of the Customer entity can be remodeled with distribution and bounded context partitioning in mind.

Each of the entities in Listing 16-12 contains a chunk of functionality taken from the bloated Customer entity previously introduced in Listing 16-11. All these entities have a CustomerId that enables the information from them to be combined, even though they reside in different bounded contexts. This solution is likely to prove significantly less problematic in a distributed system. And, as a bonus, the inclusion of bounded contexts better aligns with the problem domain.

Even before DDD was being applied to distributed systems, the notion of decomposing systems into bounded contexts and apportioning responsibilities, like those of the Customer entity in Listing 16-11, has always been a strong recommendation. Therefore, the guidance presented in this section around distribution actually applies to any DDD implementation that has, or can benefit from, multiple bounded contexts.

Common Entity Modeling Principles and Patterns

In this section you are introduced to a few principles and patterns that can improve the expressiveness and maintainability of your entities. These aren’t the only patterns, though. In fact, the aim of this section is to show you that there is room for creativity when implementing entities, assuming that the fundamentals in the previous section have been considered first.

Implementing Validation and Invariants with Specifications

Specifications are small, single-purpose classes, similar to policies. The benefits of using specifications for validation and invariants include enhanced expressiveness through encapsulating a single concept in its own class and the increased testability of immutable, side-effect-free logic. Using specifications is also an example of pushing behavior out of entities and into other types of objects, as recommended earlier in the chapter.

Listing 16-13 contains an extract of an updated version of the FlightBooking entity from earlier in the chapter. This version uses a specification to help apply its rescheduling business rule.

Basically, the logic for assessing whether the new departure date can be accepted has been offloaded into a separate class called NoDepartureReschedulingAfterBookingConfirmation, which perfectly describes the business rule. This class is shown in Listing 16-14 along with the generic ISpecification interface it implements.

A specification using this variation of the pattern has a single method, IsSatisfiedBy(), as shown in Listing 16-14. IsSatisfiedBy() should return false if the business rule being modeled cannot be applied to the passed-in object.

You may be wondering if the overhead of creating separate specifications and using a generic interface is actually worth it. It’s certainly debatable—both having separate specifications and bothering to have the interface. There’s another key aspect of this pattern, though, that benefits from the ISpecification abstraction and might convince you of its usefulness: the composition of specifications, as demonstrated in Listing 16-15.

Some airlines provide frequent flyers with tasty little perks such as short-notice rescheduling. If you wanted to update the “no rescheduling after confirmation policy” to not apply to select customers, you could model it using an OrSpecification, as shown in Listing 16-14. An OrSpecification first tries one specification, and if that fails it tries the other one. With this in mind, Listing 16-15 should now make sense; the OrSpecification first tries the FrequentFlyersCanRescheduleAfterBookingConfirmation specification, and only if that fails does it revert to the NoDepartureReschedulingAfterBookingConfirmation specification, in line with the business rules of this domain.

You can see the implementation of OrSpecification in Listing 16-16.

Building on the foundation of ISpecification, you can also compose specifications in other ways. For example, you can create an AndSpecification that requires both specifications to evaluate to true. In addition, you can create a base class or apply the builder pattern for fluently composing specifications, as suggested in Listing 16-17.

Avoid the State Pattern; Use Explicit Modeling

Many domains have entities that naturally exhibit different life cycle stages or states. In each state, usually only a subset of the entity’s behavior is applicable. For example, an online takeaway order may have the states “in kitchen queue,” “being prepared,” “being cooked,” and “out for delivery.” Obviously, when an order is out for delivery, it cannot be put in the oven, or when the order is in the oven, it cannot start being prepared because that has already happened.

Because of its life cycle, there is sometimes a temptation to model entities using the state pattern. However, some DDD practitioners strongly discourage liberal use of the state pattern for entities. Instead, the alternative is to just have explicit classes for each state.

To implement the online takeaway order scenario just described as an entity using the state pattern, Listing 16-18 shows the OnlineTakeawayOrder entity along with the interface for each state and an implementation of one example state.

Listing 16-18 accentuates the biggest criticisms of the state pattern: it can result in massive amounts of boilerplate code and unimplemented methods. These problems are made clear by InKitchenQueue, which is one of five possible states in this simplified scenario. Each implements only one method purposefully. For the methods that aren’t valid in a particular state, the state implementation throws an ActionNotPermittedInState exception. Although this example is extreme in that each state implementation handles only a single method, many implementations of the state pattern really are far noisier and more verbose than this.

More relevant to DDD is the fact that the state pattern is less explicit. By allowing methods that should not be called, the state pattern does not explicitly model the rules of the domain. If you cannot package an order that is in the oven, some DDD practitioners argue that the type system should reinforce this explicitly. You reach this ideal by having a separate entity for each state, with only the applicable operations for the state modeled as part of the entity’s interface. Listing 16-19 shows the result of applying this philosophy to the online takeaway scenario.

Listing 16-19 shows two new entities that make the IOnlineTakeawayOrderState and its implementations completely redundant. You can see that the noise has been cut down and the boilerplate is completely gone. However, the real benefit is that the rules of the domain are now much clearer in the code; no longer is there a single OnlineTakeawayOrder entity that is passed around the domain model allowing invalid methods to be called on it. Instead, the domain model must explicitly specify which state it needs to operate on, resulting in more expressive domain rules that the type system enforces.

Avoiding Getters and Setters with the Memento Pattern

If you buy into the guidance presented in this chapter that avoiding getters and setters helps to create behavior-rich domain models, you are actually quite likely to run into a common problem: getting data out of your domain models. You may want to present information on a UI or send e-mails to customers, but if the information you need is hidden away inside entities, something has to give. You may feel compelled to expose a getter, and sometimes that may be the best choice. Intriguingly, though, the memento pattern does give you another option.

With the memento pattern, you create a snapshot of your entity’s state. The snapshot is bundled out the backdoor of your entity, allowing you to harness its data in other parts of your application, like the UI. But you still maintain some of the benefits of encapsulation, because the structure of the entity’s state is still private. In essence, mementos represent a stable interface that other parts of the application can become coupled to, while the innards of your entity can still be refactored with little friction.

One part of an e-commerce domain model that might benefit from the memento pattern is a shopping basket, as Listing 16-20 illustrates.

Basket.GetSnapshot() in Listing 16-20 provides a snapshot of the Basket entity’s state—that is, a memento. By exposing its data in this way, its internal state—Id, Cost, and Items—remains encapsulated from the rest of the application.

Favor Hidden-Side-Effect-Free Functions

Side effects can make code harder to reason about and harder to test, and they can often be the source of bugs. In a broad programming context, avoiding side effecting functions as much as possible is generally considered good advice. You even saw in the previous chapter how being side-effect-free and immutable were two of the main strengths of value objects. But if avoiding side effects is good advice, avoiding hidden side effects is a fundamental expectation.

It is likely that many of an entity’s operations need to perform side effects. In particular, many methods on an entity likely need to update its encapsulated state. But this pattern of thinking can lead to the unnecessary habit of making all of an entity’s methods side effecting, and worse—home to hidden side effects. Accordingly, you should be on the lookout for opportunities to replace side effecting methods, like Dice.Value() shown in Listing 16-21, with side effect-free implementations instead.

When you focus on encapsulating state and creating behavior-rich domain models, it can be easy to write code like the Dice entity in Listing 16-21 that houses unnecessary side effects. Clients of Dice are likely to assume that calling Value() will get the value of the dice the last time it was rolled. This is because Value() sounds like a query or property. As you can see though, calling Value() has the hidden side effect of changing the value. So a client of Dice may call Value() twice expecting to see the same value, without realizing it doesn’t behave that way.

A more sensible implementation is shown in Listing 16-22. In this example Dice has a Roll() method. Roll() sounds like a command, alerting clients that this method has side effects. In addition there is a void return type making this even clearer. You can also see that Value is now a property. It sounds like a read and faithfully all it does is read the current value of the dice. No hidden side effects.

The Salient Points

  • Entities are domain concepts that have a unique identity in the problem domain.
  • Having a life cycle is also a distinguishing characteristic of entities.
  • Discussions with domain experts are a common way of uncovering entities in a domain, but you have to pay attention to their wording.
  • Value objects are the inverse of entities; they have no identity, and their equality is based on representing the same value.
  • Entities and value objects are context dependent; an entity in one domain might be a value object in another.
  • Choosing an entity’s ID is a fundamental implementation concern.
  • Natural keys from the problem domain, application generated, and datastore generated are all techniques for creating entity IDs.
  • Entities should always be valid for the given context.
  • Invariants are fundamental truths about an entity, so they should always be enforced.
  • An entity’s behavior is dictated by the needs of the application being built. It is not about modeling every behavior in the real world.
  • Be careful of modeling physical concepts as single entities. The typical Customer entity can often be logically split across multiple bounded contexts into numerous entities.
  • Be wary of using the state pattern for modeling entity life cycles; often it can be a messy way to hide domain concepts.
  • Consider patterns like the memento pattern that allow you to maintain behavior-rich domain models without exposing the structure of your entities.
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.149.249.174