20
Factories

WHAT’S IN THIS CHAPTER?

  • How factories separate use from construction
  • Applying factory methods to aggregates
  • Using a factory method for reconstruction
  • Knowing when to use a factory

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 20 download and individually named according to the names throughout the chapter.

How do you create, persist, and retrieve domain objects while maintaining a domain model that is not distracted by technical concerns? The life cycle of complex domain objects may need coordination to begin and when being persisted. Ensuring invariants are met on creation is achieved through the utilization of the Gang of Four factory pattern.

The Role of a Factory

Aggregates, entities, and value objects can become complex when you’re creating a domain model for large and rich domains. If intimate knowledge is required to ensure valid instances of a dependent object are created, it can cloud the expressiveness of the domain. The knowledge of other objects’ invariants breaks the Single Responsibility Principle (SRP). It is recommended that you separate use from construction and explicitly encapsulate creation logic within a factory object if it is complex or if it can be expressed better. Object creation is not a domain concern, but it does live within the domain layer of an application. You will rarely talk about factories to domain experts, but they do play an important role.

Separating Use from Construction

You can use factories to reconstitute a domain object from a persistence model, or you can use them to create new domain objects, encapsulating complex creation logic. The factory method pattern belongs to the creational group of the Gang of Four design patterns and handles the issue of creating objects without specifying the exact class of object to be created.

The main objective of the factory pattern is to hide the complexities of creating objects. Complexities can include deciding what class to instantiate if a client depends on an abstraction, or it could be checking invariants. A secondary objective of a factory is to clearly express the intent behind variations of an object instantiation, which is typically hard to achieve using constructors alone. The standard implementation of a factory class is to have a static method that returns an abstract class or interface. The client usually, but not always, supplies some kind of information; using the supplied information, the factory then determines which subclass to create and return. The ability to abstract away the responsibility of creating subclasses allows your client code to be completely ignorant of how dependent classes are created. This follows the Dependency Inversion Principle. Another benefit of the factory method pattern is that you centralize the code for the creation of objects; if a change is required in the way an object is generated, it can be easily located and updated without affecting the code that depends on it.

Encapsulating Internals

When you’re adding elements to an aggregate, it’s important not to expose the structure of the aggregate. Listing 20-1 shows how an application service needs to have detailed knowledge of the basket to add items to it.

In Listing 20-1, the application service method is required to understand the logic behind how a BasketItem is constructed. This is a responsibility it should not have as it should be concerned with coordination only.

You can avoid exposing the internals of the aggregate by adding a factory method. Listing 20-2 shows the Basket object with a new Add method that now hides the implementation of how the basket stores items from the application service. You can see that the responsibility has been shifted, with the Basket aggregate able to ensure the integrity of its internal collections because it can enforce invariants. The client, the application service, is much simpler now and has no knowledge of how the Basket stores products.

However, there is still the issue of the Basket being responsible for creating dependencies of the BasketItem that it does not own (the tax rate). To create a valid item from a product, the Basket needs to supply a valid tax rate. To create this tax rate, it relies on a tax rate service. The Basket has now taken on a secondary responsibility in that it must always understand how to create a valid item and where to obtain valid tax rates.

To avoid the issue of the Basket being responsible for more than it needs to and to hide the internal structure of the BasketItem, you can introduce a factory object to encapsulate the creation of the BasketItem, including the sourcing of a correct tax rate. Listing 20-3 shows the updated code and how it delegates to the BasketItemFactory. If there are changes to how tax rates are calculated or if the BasketItem needs other types of information, the Basket class is unaffected.

You could have called the service within the constructor of the BasketItem, but this is not a good idea. Constructors should be simple. If you find you have complex code within a constructor, it may be a sign that you need a factory.

Hiding Decisions on Creation Type

You can also use a factory in the domain layer to abstract the type that a class requires if there are multiple choices and if this choice is not the responsibility of the client class. The client codes against an interface or abstract class and leaves the Factory class responsible for creating the concrete type if the correct type can’t be anticipated.

Listing 20-4 shows that an order can create consignments; this is itself a factory method. What is interesting in this method is that to create a valid consignment, you must select a Courier. The Order class doesn’t know which Courier to create, so it delegates to a CourierFactory and works against a Courier abstract class. The factory creates the specific implementation.

The factory class itself delegates to a method on the Courier implementations to check whether they can handle the consignment items. Inside the constructor of each courier implementation, the factory class also checks that it can satisfy the request using the same method internally.

Factory Methods on Aggregates

Factories don’t always need to be standalone static classes. A factory method can exist on an aggregate to hide the complexities of object creation from clients. In Listing 20-5, the Basket class, which you looked at previously, now exposes the ability to create a WishListItem from a BasketItem. The resulting WishListItem object is an entity for the WishList aggregate. The Basket has nothing to do with the WishListItem after its construction, but it does contain the data required for it. The factory method removes the need for the client, the application service, to know how to extract a BasketItem and turn it into a WishListItem, by moving this control into the Basket where it naturally fits.

A factory method can also create an aggregate itself. Listing 20-6 shows that you can use an Account to create an order if there is enough credit within the account.

You can have another factory method that calls a different constructor on the Order object if you want to bypass the credit checking. You can see in Listing 20-7 that without the factory methods, it would be difficult to understand the intent behind multiple constructors for the Order object.

Factories for Reconstitution

If you are not using an object relational mapper that can map a data model to your domain model directly with reflection or if you are retrieving your domain objects from a legacy system, be it via a web service or some kind of flat file, you need to reconstruct your domain objects while ensuring all invariants are met. Using a factory to reconstitute domain objects is slightly more complex than object creation.

Listing 20-8 shows that the repository retrieves the basket persistence model from an external service in a raw data-only state. The repository then delegates to a BasketFactory, which is in the domain layer, to create the Basket instance from the raw data. You can see that the BasketFactory creates a DeliveryOption object based on the raw data. It then delegates to it to ensure that it can be used for the basket items in this instance. If the DeliveryOption is not valid, it is not used; instead, the null object pattern is used to ensure that a new DeliveryOption is chosen.

When an aggregate is reconstituted, it is vital that its invariants are met; however, it is also important to realize the reality of the situation in that the object exists in the persisted state. An option with the preceding scenario could have been to throw an exception and have some kind of workflow to repair the object; however, in this instance, it was easier to build the aggregate and replace the delivery option with a placeholder so that the user could choose the appropriate strategy for delivery.

Use Factories Pragmatically

Factories can be effective at de-cluttering your domain model to ensure that it remains expressive. However, they should only be used where they are effective and by no means everywhere an instance of an object needs to be instantiated. Use a factory when it is more expressive than a constructor or if it provides convenience where there is the confusion of more than one constructor. Use a factory where elements needed for construction logic are not the concern of the dependent class.

The Salient Points

  • A factory separates use from construction in the domain.
  • Knowledge of creating a complex domain object can be hidden from client code and domain objects by employing a factory class.
  • A factory can be used to create the appropriate instance of a domain object based on the needs of the caller.
  • Factory methods can encapsulate the internal state of an aggregate.
  • When you have multiple constructers for different purposes use a factory object to increase the expressiveness of the code.
..................Content has been hidden....................

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