15
Value Objects

WHAT’S IN THIS CHAPTER?

  • An introduction to the value object DDD modeling construct
  • A discussion of what value objects are and when to use them
  • Patterns that have emerged for working with value objects
  • Options available for persisting value objects with NoSQL and SQL databases

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 15 download and individually named according to the names throughout the chapter. A domain model contains entities, which are akin to characters in a movie. And just like characters in a movie, it’s often their attributes that make entities interesting or useful. You might note that an attribute of James Bond is that he has a high level of charisma. Equally, you might find a BankAccount entity interesting if it has a large Balance attribute. Modeling these important descriptive attributes is the role of a Domain-Driven Design (DDD) construct known as the value object.

Value objects have no identity. They are purely for describing domain-relevant attributes of entities, usually in the form of some quantity. Having no identity to deal with often makes working with value objects relatively pain free and enjoyable. In particular, being immutable and combinable are two characteristics that support their ease of use. You will learn about these characteristics, along with other pertinent ones, in this chapter. You will also see some common value object modeling patterns that promote usability and expressiveness.

Working with value objects can still be challenging at times because there are a few considerations that require deeper thinking: notably, persistence, validation, and primitive avoidance. This chapter covers these challenges, too, to ensure that you know everything you need to be able to create value objects of your own.

When to Use a Value Object

Value objects are an entity’s state, describing something about the entity or the things it owns. A ship may have a maximum cargo capacity, a grocery may have a stock level, and a financial report may have quarterly turnovers. Each of these relationships would likely be modeled as an entity-value object relationship. For each example, note how the value object represents a particular concept that has a measurement, magnitude, or value—hence, value object. Two key rationales make value objects an important technical construct in DDD.

Representing a Descriptive, Identity-Less Concept

If you look purposefully at value objects, like Money and others shown throughout this chapter, you will see a logical commonality. It would not make sense for them to have an identity. Only entities may have an identity. A basic example of this is a bank account; you would almost certainly need to look up a BankAccount entity by its ID, but would you look up its balance by its ID? In most domains, you wouldn’t because the balance has no meaning or importance in isolation.

Listing 15-1 shows a BankAccount entity whose balance is represented by a Money object. After the previous discussion, hopefully it’s clear why this modeling decision is sensible.

When a concept lacks an apparent identity, it’s a big clue that it should be a value object in your model. Later in the chapter, you will gain a better understanding of this when further examples of value objects are shown. In particular, understanding their defining characteristics is key to being successful with value objects.

Enhancing Explicitness

DDD is all about explicitly communicating important business rules and domain logic. Conversely, primitive types like integers and strings in isolation aren’t great at this. Although it is possible to represent descriptive concepts with primitive types, most DDD practitioners strongly discourage it. Instead, primitives should be wrapped up into cohesive value objects that explicitly represent the concepts they are modeling.

Listing 15-2 shows an object representing the current winning bid in an online auction. To represent the price of the bid, in money, an integer is used.

Representing the amount of the winning bid as an integer in Listing 15-2 is a sub-optimal design choice for two major reasons. One reason is that the integer does not express what a price is in this domain—it doesn’t restrict inputs to the allowed range of values, and it doesn’t express the unit of measurement or currency. This is a massive source of ambiguity that hides important details of the domain.

By modelling the winning bid amount as an integer, there is also a big risk that related domain concepts will be scattered throughout the domain rather than being cohesively co-located, because you cannot add behavior to primitive classes (nor would it make sense to).

In Listing 15-3, the Price value object shows that in the online auction domain, the rules for incrementing the winning bid for a price are significant. You can see that the benefit of the Price value object is to cohesively group all the related behaviors of a price: BidIncrement() and CanBeExceededBy(). This helps to express and enforce the rules of the price concept in this domain. Whenever there is a discussion involving prices, developers and domain experts need only look at this one single class to understand what a price is and what rules apply to it.

As Listing 15-3 shows, by wrapping primitives with domain concepts, the type system does the mapping from data to domain concept for you, and the lack of clarity goes away. Consequently, your conversations with domain experts can be clearer and domain concepts will be more apparent to anyone reading the code. In addition, all functionality related to a single concept is modelled cohesively. In this instance, the rules for incrementing a price and determining if a price can be exceeded by another price are the cohesive grouping of behaviors related to the price concept that should and do live together.

Listing 15-3 also illustrates how value objects should often be fine-grained. The Price value object itself pushes the related concern of representing money into the Money value object, using an instance of Money to represent the amount of the winning bid. You will the see Money value object later in this chapter.

Defining Characteristics

Being mostly self-contained is what makes value objects fundamentally easier to work with. Like proponents of functional programming, DDD practitioners are fond of value objects because they are immutable, side effect free, and easily testable. As you will see in the following examples, they do have a few other defining characteristics that are important to be aware of.

Identity-Less

As you learn about value objects, the single most important detail to remember is that they have no identity. Value objects are important because they tell you something about another object. How tall is a Person entity? How many years have there been no claims on an InsurancePolicy entity? What is the weight of a Fruit entity? Hopefully you can discern from these examples why having an identity would make no sense.

Because they have no identity and describe other concepts in the domain, normally you will uncover entities first, and from there realize the types of value objects that are relevant to them. Generally, value objects need the context of an entity to be relevant.

Comparing value objects is a crucial operation, though, even though they have no identity. This is enabled through attribute- or value-based equality.

Attribute-Based Equality

Entities are considered equal if they have the same ID. Conversely, value objects are considered equal if they have the same value.

If two Currency value objects represent the same amount of money, they are considered equal, regardless of whether each variable points to the same object or a different one. Likewise, if two Temperature value objects represent 30 degrees Celsius, the same applies. Listing 15-4 illustrates a Meters value object used in the domain model of a personal fitness system that tracks how much distance has been covered. Listing 15-5 then shows test cases demonstrating its attribute-based equality.

Listing 15-4 shows how the Meters value object overrides Equals() to implement attribute-based equality. By default, C# (and other languages like Java) will consider two objects to be equal if they point to the same object reference or pointer. With a value object, you don’t care if the two references point to the same object, you care about whether they represent the same domain-relevant value. In Listing 15-4, you can see the implementation of Equals() fulfils this obligation by returning true, indicating the two objects are equal, if they represent the same distance in meters to two decimal places (precision will vary based on domain rules and context).

Listing 15-4 shows only the minimum amount of code necessary to demonstrate attribute-based equality. To get full equality support in C#, you actually need to override a few other methods that the compiler and run time require for various scenarios. Implementing all these methods in all your value objects can get a bit tiresome, especially when it is unnecessary. As shown in Listing 15-6, you can employ a base class to prevent unnecessary boredom and repetition.

To ensure that your classes support all the native comparison operations in C#, you need to implement Equals(), GetHashCode(), and the operator overloads == and !=. Using a base class like the one shown in Listing 15-6 makes your life easy by doing all this for you. All that’s left for you to do is implement GetAttributesToIncludeInEqualityCheck() in each sub class, as shown in Listing 15-7. If you are prepared to add another layer of complexity, you can investigate reflection-based alternatives.

Listing 15-7 shows an alternative implementation of Meters with the same semantics and behavior. In addition it also supports == and !=. This is a satisfying improvement over having to repeatedly apply the boilerplate that the ValueObject base class now manages.

Behavior-Rich

As much as possible your value objects should expose expressive domain-oriented behavior and encapsulate state. This was demonstrated with Listing 15-4 where the Meters value object exposed the behaviors: ToFeet(), ToKilometers(), Add(), and IsLongerThan(). Importantly, note also how it encapsulated its DistanceInMeters state. As a general rule, all primitive values should be private or protected by default. Only when you have a very good reason should you break encapsulation and make them public. But first consider adding a method to the value object that provides the required functionality.

As you will see repeated throughout this book. Focusing on behavior to create behavior-rich domain models is one of the most crucial aspects of creating and evolving a domain model, so that domain concepts are explicit. This applies equally to value objects and other types of domain objects that are introduced later in the book.

Cohesive

As a descriptive concept, usually describing something with a quantity, value objects often cohesively encapsulate the value of measurement and the unit of measurement. You’ve seen this already with the Height value object, which encapsulates the size and the unit of measurement. Similarly, you also saw this with the Money value object, which encapsulates both the amount and the currency.

It’s not always the case that being cohesive means encapsulating a value and unit of measurement, though. It could be any number of fields. For instance, a Color value object may have a Red, a Green, and a Blue property.

Immutable

Once created, a value object can never be changed. Instead, any attempts to change its values should result in the creation of an entirely new instance with the desired values. This is because immutability is usually easier to reason about, with fewer dangerous side effects.

A classic example of immutability, and generally a good role model for value objects, is .NET’s DateTime class. The unit tests in Listing 15-8 exemplify how calling any operations that appear to mutate the object’s state, including AddMonths() and AddYears(), actually return a completely new DateTime object.

Implementing immutability yourself is easy. First, you decide how you are going to expose state. One option is to have readonly instance variables, as shown in Listing 15-9. The other approach is to have properties that are set in the constructor and never altered, as previous listings demonstrated. Both approaches are common, and it’s usually just a matter of personal preference or project conventions. Sometimes, though, frameworks may force you into one choice or the other.

In addition to the state itself, you expose descriptive method names that express the creation of the new value in domain-oriented terms. For DateTime, those methods include the previously discussed AddMonths() and AddYears(); for the Money value object in Listing 15-9, the methods are Add() and Subtract().

The Money value object in Listing 15-9 has two methods that sound like they mutate its state: Add() and Subtract(). Looking at the implementation tells a different story, though, because both methods return a new instance of Money with the updated value, while the original instance is left completely unchanged. Being immutable also supports combinability, as the next example illustrates.

Combinable

Values are often represented numerically, so in a lot of cases, they can be combined to create a new value. As you saw in the previous example, Money can be added to Money to create a new amount. Combinability like this is a defining characteristic of value objects in general. So when you are with domain experts and they talk about combining two instances of a certain concept, this is a clear sign that you may need to model the concept as a value object.

Through representing a value or quantity, in most cases value objects can be combined using operations like addition, subtraction, and multiplication, as the previous immutability example demonstrated. For enhanced expressiveness, you can override these native operations, on a per-object basis, in many programming languages. Listing 15-10 shows how the Money value object can be updated to support the plus + and minus - operators in C#.

Overriding the + and - operators is light work in C#, as Listing 15-10 shows. You can see unit tests in Listing 15-11 that demonstrate using these operators to combine immutable Money objects into new instances.

Self-Validating

Value objects should never be in an invalid state. They themselves are solely responsible for ensuring this requirement. In practice, this means that when you create an instance of a value object, the constructor should throw an exception if the arguments are not in accordance with domain rules. As an example, if you are modeling money with a Money value object in an e-commerce application, there may be two important domain rules:

  • All money is accurate to two decimal places.
  • All money must be a positive value.

These two rules apply to all instances of Money in the domain and should never be violated. Listing 15-12 shows a Money value object that enforces these constraints in its constructor, ensuring it can never be created in an invalid state.

The first thing that happens in Money’s constructor is the call to Validate(), which enforces the important self-validating characteristic of value objects. Implementation-wise, you don’t have to go to a lot of effort. As Listing 15-12 shows, in many cases, all you need to check is that the state of the value object is within the allowable range. If it’s not, you just throw a descriptive exception and abort construction as soon as possible.

Other coding patterns do exist for enforcing validation, though, depending on your preferences. Each pattern’s flexibility and expressiveness can vary based on your context, so it’s worthwhile knowing about them. The first alternative pattern is to validate inside a factory method, as shown in Listing 15-13.

Notice in Listing 15-13 how Create() is static. This is the factory method that is used as an alternative to validating in Money’s constructor. You may want to use this pattern when value objects can be created in different states based on the context. For example, in some scenarios, it may be fine to allow negative money, but not in others.

Using factory methods does mean that you can bypass validation altogether and create an instance with the new keyword. So use it with caution. If you have a situation in which different contexts require different validation rules, you should definitely check out the introduction to micro or tiny types later in this chapter.

Another pattern for validating value objects is to use code contracts, which trade off additional technical complexity with greater expressiveness and fluency. You can use code contracts to supplement either of the previous patterns mentioned. Listing 15-14 illustrates using code contracts with constructor validation.

If you do prefer code contracts, you can minimize the additional complexity by creating reusable contracts and helper objects. Listing 15-15 shows a few basic code contract utilities that support the example in Listing 15-14. You are free to build a library of these helpers yourself or consider existing code contracts libraries such as the official Microsoft offering (http://msdn.microsoft.com/en-us/library/dd264808(v=vs.110).aspx).

Testable

Immutability, cohesion, and combinability are three qualities of value objects that make them easy to test in expressive domain-oriented language. Immutability precludes the need to use mocks or verify side effects, cohesion allows single concepts to be fully tested in isolation, and combinability allows you to express the relationships between different values.

Throughout this section, you’ve already seen a number of examples of unit tests. But they were shown in the context of other characteristics, so you may not have taken the time to appreciate how low friction they are. Listing 15-6 is another short example showing the testability of value objects, demonstrating how error conditions can easily be tested without mocking.

By observing the tests in Listing 15-16, it is easy to discern the lower levels of friction when compared to testing other objects like application services. This is mainly due to the lack of side effects and mutability. You’ll often hear proponents of functional programming lauding these characteristics. In essence, value objects themselves are a functional concept (or at least very close).

Common Modeling Patterns

DDD practitioners have built up a small collection of patterns over the years that improve the experience of working with value objects. Mostly, the benefits are aimed at improving expressiveness and clarity, but some have other slight benefits, including maintainability. This section presents three basic patterns so that you can immediately start to use them and start to think about patterns of your own, too.

Static Factory Methods

Using static factory methods is a popular technique for wrapping the complexities of object construction behind a simpler, more expressive interface. An excellent example of this is .NET’s TimeSpan class with its FromDays(), FromHours(), and FromMilliseconds() static factory methods. These alternatives are more expressive and less ambiguous than the five-integer-parameter constructor, as Listing 15-17 illustrates.

Static factory methods are a stylistic choice that you are free to choose and ignore as you wish. Listing 15-18 shows the change that is made to the previously shown Height value object by having a static factory method for each unit of currency. In this case, the code is arguably more expressive, easier for clients to call, and more maintainable because clients of the code no longer need to couple themselves to the MeasurementUnit enum.

Micro Types (Also Known as Tiny Types)

Avoiding primitives can help you be more explicit about the intent of your code by reducing error-causing ambiguity. This was already discussed previously in this chapter. A pattern called micro types takes this principle even further by wrapping already-expressive types with even more expressive types. To clarify, with micro types, the types being wrapped do not have to be primitives; they can already be explicit concepts that wrap primitives. This is useful, arguably, because it adds contextual clarity that can reduce errors.

An example of micro types is presented initially with the OvertimeCalculator domain service shown in Listing 15-19. Note the two parameters of type HoursWorked and ContractedHours.

Both the HoursWorked and ContractedHours types used in Listing 15-19 are micro types that wrap an Hours value object. OvertimeHours is the return micro type, which is also just a contextual wrapper for Hours. All four class definitions are shown in Listing 15-20.

As you can see from Listings 15-20 and 15-21, HoursWorked and ContractedHours add no additional behavior or state. It would be just as easy for OvertimeCalculator.Calculate() to accept two Hours instances. But doing so emphasizes making the parameter names explicit and relying on callers to supply the two Hours objects in the correct order. Using micro types, you make the type system work harder for you so that it both increases the explicitness of your code and prevents human error.

Using micro types is far from an industry best practice. In fact, it’s quite divisive. Some claim micro types are a precursor to clearer, more composable code, but for others, micro types are too many layers of annoying indirection. It’s up to you to decide if you want to use the micro types pattern.

Collection Aversion

Some DDD practitioners feel that you should never have a collection of value objects. The rationale for this is that primitive collections do not properly express domain concepts. It is also argued that having a collection of value objects often means that you need to pick out specific items using some form of identity, which is clearly a violation of value objects having no identity. Again, though, this is not a universally applied practice within the community, but it is one that you should have a good justification for eschewing.

An example that highlights the lack of clarity through exposing a value object collection is the Customer entity presented in Listing 15-22, which has multiple PhoneNumber value objects.

There are multiple PhoneNumbers that are part of the PhoneNumbers collection. Why? Could they be home and cell phone? Perhaps there’s an emergency contact or work number in there, too. It’s just not clear by using a collection. But in this domain, PhoneNumbers are structured according to business requirements. Each customer must supply a home, mobile, and work number.

A more honest way of modeling the domain is shown in Listing 15-23, which clearly uses structure to model the important domain concepts of a home, mobile, and work phone number. It also makes it easy to change any of these numbers without requiring an ID lookup or a nasty hack.

In Listing 15-23, the modified Customer entity now has its PhoneNumbers represented by a PhoneBook value object. Each type of phone number—home, work, and mobile—can now be accessed without requiring any type of ID look-up. This is important because, as has been repeatedly mentioned, value objects do not have an identity.

Most importantly with Listing 15-23 compared to Listing 15-22 is that the intent and domain concepts are much clearer. Customer entities have a home, work, and mobile number. This important domain structure is encoded into types and enforced by the type system.

Persistence

Arguably, the trickiest aspect of dealing with value objects is persisting them. With document-oriented data stores like RavenDB and EventStore, this is a lesser problem; with these technologies, it’s usually feasible to store the value object and the entity in the same document. With SQL databases, however, there is a strong tradition of normalization, which leads to more variability concerning implementation. Therefore, the remainder of this chapter mostly focuses on SQL-based use cases.

NoSQL

Many NoSQL databases use data denormalization, which highly contrasts with the strong convention of normalizing SQL databases. NoSQL can be beneficial to DDD because entire entities—sometimes entire aggregates—can be modeled as single documents. Problems associated with joining tables, normalizing data, and lazy loading via ORMs just don’t exist with document-oriented modeling. In the context of value objects, this simply means that they are stored with the entity. For example, the Customer entity and Name value object shown in Listing 15-24 can be stored as the single, denormalized JavaScript Object Notation (JSON) document shown in Listing 15-25.

A number of document-oriented NoSQL databases persist documents as JSON. Listing 15-25 shows how a Customer and its Name value object can be persisted in such a database as a single JSON document. Document databases like RavenDB usually apply this convention by default, meaning that you just have to create an appropriately structured object model.

Although embedding value objects in documents is a common convention, it is still an optional one. You can store value objects as separate documents for performance reasons, if necessary.

SQL

Persisting value objects in a SQL database comes with choices. You can follow standard SQL conventions and normalize your value objects in their own tables, or you can denormalize them in-situ akin to the document-oriented approach shown in Listing 15-25. This section shows an example of each of those two broad approaches. It’s worth pointing out that these examples are just a guide. There are many variations possible with each approach, so you are free to customize and experiment based on your own needs.

Flat Denormalization

A common pattern for persisting value objects is to just store their value directly using a custom representation. This is a great choice when you don’t want to have extra tables and extra statements in your queries to join them.

DateTime is a good template for persisting value objects directly because they are stored in a SQL database as a textual representation. There is no separate DateTime table that stores each value used in the application. In fact, DateTime is such a common value that it has its own database type. You can model your own value objects in this way, too, even without having an explicit database type. To achieve this, though, you need to create your own storage format, and you may need to teach your frameworks and ORMs how to use it.

Creating a Persistence Format

To be able to repopulate value objects when they are loaded from persistence, you are required to choose a format that uniquely identifies each possible value. A value object can then be persisted in this format when it is saved to the database and parsed from this format when it is loaded from the database.

Overriding ToString() is one possible pattern for generating a value object’s persistence format. Because this format uniquely describes a value object, it can also be good for debugging. Listing 15-26 shows an updated Name value object that overrides ToString() to return a unique description of the value it represents, with the intention of persisting the object in that format. Listing 15-27 then shows some unit tests demonstrating how it works.

Creating a persistence format is chiefly about creating a unique representation for each value. This is the minimum requirement for this approach of persistence to work. In Listing 15-26, ToString() is overridden to produce a human-readable representation. In your applications, you can also favor human-readable, but sometimes you may want to prefer formats that have a smaller footprint or even another quality. When choosing a storage format, it’s also worth keeping in mind that at some point you may need to filter SQL queries based on the values.

Another optional aspect of the implementation shown in Listing 15-26 is actually overriding ToString(). This was done for the dual benefit of persistence and debugging enhancements. But if you wanted a separate ToString() and persistence format, you could either overload ToString() or create another method, perhaps ToPersistenceFormat(). The most important detail is ensuring that whichever method generates the value to be persisted is called at the time of persistence by your handcrafted SQL queries or your choice of ORM.

Persisting Values on Save

This example shows you how to persist a value object using an ORM (the most complex case). If you are persisting with handcrafted SQL queries, it should be easy to pass the custom representation of your value object into the INSERT or UPDATE query.

To persist your value object, the most important step is instructing your ORM. In NHibernate, you can do this by creating a customer IUserType, as Listing 15-28 shows. Most ORM frameworks are likely to have a similar feature.

NHibernate’s IUserType allows developers to manually take control of persisting specific types of objects. In Listing 15-28, NameValueObjectPersister implements the IUserType interface to control how Name value objects are persisted. As you can see in Listing 15-28, IUserType.NullSafeSet() is where your custom logic is inserted to generate the value that is stored in the database.

Parsing Values on Load

Having told your ORM to persist your value object in a custom format, or having manually inserted the value yourself, the remaining task is to handle loading of the object by parsing the representation back into a value object. There are a few possible approaches to implementing this. First, you could have a constructor that takes a string representation of the value object. Alternatively, you could have an intermediate component that parses the string and constructs an instance in the normal way. This example uses the latter approach to avoid any persistence or framework-related concerns creeping into the value object.

As with persisting, the key detail is finding the framework hook that allows you to inject your custom parsing logic. With NHibernate, that hook is another method on the IUserType that was previously introduced. You can see how to parse a Name value object from its persistence representation in Listing 15-29.

IUserType.NullSafeGet() is the low-level NHibernate hook that allows you to manually reconstruct an object from its persisted state. In Listing 15-29, the implementation of NullSafeGet() extracts the FirstName and Surname from the plain-text representation of the Name value object that is being loaded and uses them to create a new Name instance. As mentioned, whichever ORM you are using, there is likely to be an equivalent hook that gives you low-level control for storing value objects in a custom format and parsing them back again.

You can see the full implementation of NameValueObjectPersister with this chapter’s sample code, including unit tests that fully configure NHibernate before storing and loading sample data. Figure 15.1 shows an example of what is stored in the database after running one of the tests.

images

FIGURE 15.1 Value object stored in custom format.

Normalizing into Separate Tables

Denormalization is the de facto persistence strategy for value objects, but normalization is a strong SQL tradition. Sometimes company standards may even enforce the latter. Normalization can still be the best choice in some situations, though. Performance and efficiency reasons would be common examples—especially if the value object has a large representation that you do not want to be loaded every time the owning entity is loaded.

In this example, you see how to map an entity-value object relationship, where each type has its own table, and where a foreign key joins the two. As in the last example, NHibernate will be used to demonstrate this scenario, but the general pattern can likely be applied with whatever technology you are using.

For NHibernate to store the Name value object in a new table, the first adjustment is to add a protected zero-argument constructor, as Listing 15-30 illustrates.

Surprisingly, there is little additional work you need to do. When using Fluent NHibernate, you need only configure a Join() mapping relationship, as shown in Listing 15-31.

By using a Join() mapping instruction, the code in Listing 15-31 is telling Fluent NHibernate to create a separate CustomerName database table that stores Name value objects. The KeyColumn() instruction tells NHibernate to use the customer’s ID as a foreign key in the CustomerName table. So when loading a Name occurs at run time, a Customer entity can find its Name by supplying its own ID. Whichever technology you are using, there is likely to be some convention, configuration, or hook that allows you to achieve something similar.

Figure 15.2 illustrates the tables that NHibernate creates when you run the preceding configuration. Additionally, you can see in Figure 15.3 how persisted data is stored—notably, the Customer’s ID is being used as the foreign key.

images

FIGURE 15.2 Separate table for Customer and Name.

images

FIGURE 15.3 Customer foreign key used to join Customer and Name.

The Salient Points

  • Value objects are DDD modeling constructs that represent descriptive quantities like magnitudes and measurements.
  • Due to having no identity, you do not have to lumber value objects with the complexities associated with entities.
  • You are encouraged to wrap primitive types with integers and strings so that domain concepts are articulated well in the code.
  • Examples of value objects include Money, Currency, Name, Height, and Color. However, it’s important to remember that value objects in one domain might be entities in another and vice versa.
  • Value objects are immutable; their values cannot change.
  • Value objects are cohesive; they can wrap multiple attributes to fully encapsulate a single concept.
  • Value objects can be combined to create new values without altering the original.
  • Value objects are self-validating; they should never be in an invalid state.
  • A number of modeling patterns exist for working with value objects, but you can also create your own.
  • You can persist the value of value objects directly in a denormalized form. This is the most common and easiest case to implement with document-oriented databases and is still a popular approach with SQL.
  • You can also persist value objects as their own documents or tables. This is still a common option for SQL databases, but it’s often an optimization when applied to document databases.
..................Content has been hidden....................

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