Graduate Program KB

The Life Cycle of a Domain Object

  • Every Object has a lifecycle

    • Object is Born
    • Object goes through many different states
    • Object dies - becomes archived or deleted
  • Not all objects have the same lifecycle, some may live longer than others

  • These objects have complex interdependencies with other objects

  • Managing these objects have challenges that can derail the attempt at MODEL-DRIVEN-DESIGN

  • Challenges:

    1. Maintaining integrity throughout the lifecycle (Integrity: Means the Object's expected behaviour and properties is consistent, reliable and correct.)
    2. Preventing the model from getting swamped by the complexity of managing the life Cycle
  • This chapter will address these challenges with 3 patterns

    1. Aggregates: addresses integrity of all phases in life cycle
    2. Factories: addresses the beginning of life cycle
    3. Repositories: addresses the middle and end life cycle
  • Using these 3 patterns gives the ability of manipulate the model objects systematically

  • Factories and Repositories operate on aggregates, by encapsulating the complexity of specific life cycle transitions

Aggregates

  • Tightens the model itself by defining clear ownership and boundaries, this is to avoid chaotic, tangled web of objects.

  • Crucial for maintaining integrity of all phases of the life Cycle

  • Problems/Issue:

    • Minimal design of association helps simplify relationships
    • Most business domains are so interconnected, it becomes a tangled representation of the realities of the world
    • Example: If you are deleting a Person object, a person object could have a name or a birthday. What happens if they have an address, if you delete the address, other Person object could be referencing that deleted address. You could still leave it in there, but there would be a lot of junk address persisted. Even with garbage collection it ignores the a basic modelling issue.
    • Difficult to guarantee the consistency of changes to objects in a model with complex associations.
    • With multiple users consulting and updating objects in the system, we have to prevent simultaneous changes, but having a locking scheme can cause multiple users to intefere with each other and make the system pointless
    • In any persistent data storage, there much be a scope for changing the data and maintaining consistency of data
    • Databases allow locking schemes but these are ad hoc solutions and divert attention away from the model
    • Finding a balanced solution calls for a deeper understanding of the model
  • Aggregate: is a cluser of associated objects, which is treated as a unit as a purpose for data changes

  • Each aggregate has a root and a boundary

    • Boundary: defines what is inside aggregate
    • Root: a single specific entity contained in the aggregate. Is the only member in the aggregate that outside objects can hold a reference to. But objects within the same boundary can hold references to each other.
    • Entities other than the root have local identity, but the identity is only distinguishable within the aggregate, because no other outside object can see it out of context of the root entity
  • Example: Model of car used in software. The car is an entity with global identity. We can use the cars id. Might want to track each individual tire for their mileage, and we can make the tires entities with identifiers, but outside of the context of this particular car, we don't really need to care about each tire.

    • The car is the root Entity of the aggregate which has a boundary to hold the tire entities
    • Even the engines could be a root entity of its own aggregate
    • Overview Diagram:
      • Car made up of a 4 tires
      • Car made of a 4 wheel which then have their own position
      • The line around is the aggregate boundary
      • Objects outside of the boundary, can not hold a reference to any of the objects inside, except for the root entity which is the car
    • Invariants are rules that must be maintained when data changes
    • Rotate is the only method that have external access to the change the tire position
  • Set of Rules to apply to all transactions:

    • Root entity has global identity, and is responsible for checking Invariants
    • Root Entities have global identity, Entities inside the boundary have local identity and is only unique within the aggregate
    • Objects outside of the boundary should not hold a reference to anything inside, except to the root Entity
    • The root entity can hand references to internal entities to outside objects but only use them transiently (Transiently: can temporarily be used for a specific purpose or duration. Relationship is temporary and short-lived). Root entites can give out copies of value objects to outside objects, and they can do whatever they want with it, since it is not associated with the aggregate.
    • Aggregate roots can be obtained directly with database queries
    • Delete operation must remove everything within the aggregate boundary, easy because there should not be any outside objects that are referencing them
    • When there is a change inside the aggregate boundary, all invariants of the whole aggregate must be satisfied
  • Cluster the entities and value objects into aggregates and define boundaries around them.

  • Choose 1 entity to be the root of each aggregate, and control access to the objects inside through the root

  • Purchase Order Integrity Example

    • Invariant Rule: sum of the line items can't exceed the limit of the PO approved limit
    • 3 interrelated Issues:
      1. Invariant Enforcement: when new line item is added, PO checks the total and marks itself invalid if item pushes over the limit
      2. Change Management: When PO is deleted/archived, line items are taken along, but not guidance on where to stop following the relationships. Also confusion about the impact of changing the part price at different times
      3. Sharing the database: Multiple users creates contention problems in the database
    • First way, is locking access to an line item being editted, issue occurs when both users edit different items and then the invariant rule is broken, when it is saved
    • Second way, is to lock access to entire purchase order. This way the problem can be solved, but this is only the case for a simple small PO. Issue is with bigger PO, multiple people would have to wait to edit.
    • Even changing the price of the item can cause issues
    • Locking the part (Line Items) causes lots of contention for these items. Interferes with other peoples PO when they have the same items
    • Improve the model by incoporating the knowledge of the business:
      1. Parts used in many PO (High Contention)
      2. Fewer changes to parts then there are POs
      3. Changes to part prices don't propagate to existing POs.
    • Purchase Order and Line Item are in their own boundary
      • Line item has a price copied from Part
  • By making the dependency of Line Items and Parts looser, we avoid contention.

  • Tightning the relationship between PO and Line Item, guarantees that important business rules will be followed.

  • Aggregate imposes an ownership of the PO and its items, to be consistent with business rules. Creation and Deletion of PO and items are tied together. Whereas creation and deletion of Parts is independent.

  • Aggregates mark off the scope within which invariants have to be maintained at every stage of the life cycle. The patterns later in the chapter Factories and Repositories, operates on Aggregates, encapsulating the complexity of specific life cycle transitions.

Factories

  • Beginning of Cycle

  • Create and reconstitute complex objects and Aggregates.

  • When the creation of an Object or an entire Aggregate becomes complicated or reveals too much of its internals, Factories can provide encapsulation

  • Examples with Cars:

    • The job of assembling a car is unrelated to how an engine works
    • Because cars are never assembled and driven at the same time, you don't need the assembler to be there.
  • Best to seperate assembling a complex object and the job of the object

  • Combining creation and objects responsibilities produces designs that are hard to understand

  • Making the client being able to construct the object breaches encapsulation and abstraction because the client has know the internals of the object. Which makes the client coupled with the implementation of the object

  • Object creation responsibilities are for the domain layer

  • A program element whose responsibility is the creation of other objects is called a Factory

  • Client --(Specifies what they want)--> Factory --(Factory creates object based on the specification from client)--> Object/Product

  • Factory encapsulates the knowledge needed to create an Object or aggregate, so clients can use the object without knowing the internals of how it works

  • Shift responsibility for creating instances of objects and aggregatess to a seperate object.

  • Provide an interface that encapsulates all assembly and does not require the client to reference the concrete classes of the objects being instantiated.

  • Many ways to design Factory: Factory Method, Abstract Factory, Builder

  • Proper use of Factories can keep the MODEL DRIVEN DESIGN on track

  • This book is not about designing Factories

    • Good Factory Requirements
      1. Each creation method is atomic and enforces all invariants of the created object or aggregate. Should produce an object in a consistent state. (atomic: method designed to be executed without interference)
      2. Should be abstracted to the type desired rather than the concrete classes

Choosing FACTORIES and Their Sites

  • Use a factory to build an object whose details you want to hide

  • Create a factory method on the root of the aggregate, this hides the implementation of the interiors of the aggregate from outside objects.

  • Example Purchase Order:

    • Factory Method to create the entire aggregate
  • Can also create a factory method on an object that is closely involved in creating another object

  • A factory method can create an entity that is not part of the same aggregate

  • Example Brokerage Account:

    • Rather than the client creating the Trade Order, we let the Brokerage Account create the Trade Order Entity
  • A factory is tightly coupled to its product, factory should be attached only to an object that has a close relationship

  • If an object interior to an aggregate needs factory, and the aggregate root is not a suitable home, then create a standalone factory

When a Constructor is All You need

  • Factories can obscure simple objects that don't use polymorphism
  • Avoid calling constructors within constructors of other classes. Cosntructors should be dead simple
    • Hard to test, because it depends on the first constructor
  • Complex assemblies call for Factories
  • Simple constructs call for constructors
  • Abstract collection, outsiders dont need to know the concrete

Designing the Interface

  • When designing the method signature of a Factory we need to have 2 points in mind
    • Each operation must be atomic:
      • You have to pass in everything needed to create a complete object in a single interaction
      • because the invariants can be held
    • The factory will be coupled to its arguments:
      • If arguments are plugged into the object, it is a modest dependency
      • If you are picking parts out of the arguments, the coupling gets tighter
  • Safest parameters are those from the lower design layer
  • Another good choice of parameters, is an object closely related to the product of the model, so no dependencies are added
  • Use abstract type of arguments and not their concrete classes. Factory is coupled to the concrete class of the product, and does not need to be coupled with the concrete parameters
  • Concrete arguments contain more information that is needed, abstract contains just the information needed to create an object

Where Does Invariant Logic Go?

  • The Factory is the logical place to put invariants, keeping the products simpler
  • Keeps product simpler, because reduces clutter in the product.
  • The product doesn't really need to carry around logic that will never be applied in its active lifetime.

Entity Factories vs Value Object Factories

  • Entity Factories take the essential attributes required to make an valid aggregate, and details can be added later
  • Value Object Factories, need to make the final form of the product, because value objects are immutable
  • Previous chapter, identifier for the Entity, can be assigned automatically by the program or supplied from the outside

Reconstituting Stored Objects

  • At some point in the object life cycle, they get stored in databases or sent somewhere else.
  • Most of the time, the objects get flattened into a limited representation when being stored
  • Retrieval is a complex operation and reassembling the parts into a live object
  • Reconstitution Factory has 2 differences:
    1. Entity Factory use for reconstitution does not assign a new tracking id
      • Identifying attributes must be part of the input parameters when reconstituting a stored object
      • Because if you assign a new id, it would lose the continuity of the objects previous incarnation
    2. Factory reconstituting will handle violation of invariant differently
      • When you create a new object, you can choose not to create it because the invariant failed
      • But need to be flexible when reconstituting, because the object exist somewhere else
  • Factory don't express any part of the model, but are still part of the domain design
  • A Factory encapsulates the life cycle transitions of creations and reconstitution.

Accessing Domain Objects

  • To do anything with an object you must hold a reference to it

  • Two ways to get a reference to an object

    • Creating: Creation operation will return a reference to the new object
    • Traversal: You start with an object you already know and ask it for associated objects
  • Anecdote of the team that wanted to only use Creation and Traversal in their project to access objects

    • The problem is was that the objects were stored in a relational database and database queries made objects globally accessible
    • The team then thought there was no need for objects to be interconnected
    • The project lost sight of their original approach cobbling together ad-hoc solutions
  • This raises the question for the design decision of should you use traversal or a search? The answer is to use a combination of both

  • A subset of persistent objects must be globally accessible through a search based on object attributes. Usually Entities and sometimes persistent Value Objects (with complex internal structure and sometimes enumerated Values). These objects are often the roots of aggregates and provide the start point for traversal to other objects.

  • This approach is better over free database queries as queries to everything can breach encapsulation of domain objects and aggregates.

  • Persistent Value Objects

    • Itineraries being linked to a user
    • Enumeration when a type has a limited set or predetermined values

Repository Pattern

  • Conceptual framework for dealing with database access and bring back the model focus

What is a Repository?

  • Represents all objects of a certain type as a conceptual set

  • Objects of the appropriate type are added and removed

  • Machinery behind the repository inserts or deletes them

  • Clients request objects from the repository using query methods

  • The repository acts a simple interface hidin the complexity of accessing the data from the client

Advantages of Repositories

  • They present clients with a simple model for obtaining persistent objects and managing their life cycle
  • They decouple application and domain design from the persistence technology, multiple database strategies, or even multiple data sources
  • They communicate design decisions about object access
  • They allow easy substitution of a dummy implementation for use in testing (InMemory)

Querying a Repository

  • Two ways to query a database using repositories

    • Hard Coded Queries
    • Specification Based Queries
  • Hard Coded Queries

    • Easiest repository to build
    • Include queries that use a specific parameter
    • i.e retrieving by entity id or entity attribute
  • Specification Based Query

    • Allows client to describe what it wants without concern for how it will be obtained
    • An object that can carry out selection is created

Implementing a Repository

  • When implementing a repository, the goal is to hide the implementation details from the client, delegating to the appropriate infrastructure services

  • The client code must be the same regardless of what repository is being used (InMemory, Relational Database etc)

  • The repository encapsulates the mechanisms of storage, retrieval and query as the most basic features

  • Some concerns to keep in mind

    • Abstract the type: Repositories contain all instances of a type, this does not mean all instances of a class (i.e TradeOrder can be a BuyOrder or SellOrder)
    • Take advantage of decoupling from the client: Freedom to change implementation of repository. You can vary the querying technique or utilize caching to improve performance. Testing you can use inMemory
    • Leave transaction control to the client: Let the client commit units of work. Repositories should not initiate changes to the database unless directly asked to

Working within your framework

  • If you have the freedom to choose a framework, making it harmonious with the style of design you want to use
  • If you already have a framework in place
    • Look for existing patterns and existing services of getting persistent objects. Utilize them when implemeting your repository so that you dont have to fight with your framework

The relationship with factories

  • A factory creates new objects (handles the beginning of an objects life)

  • A repository finds old objects (manages the middle of a objects life)

  • When objects arent stored in memory or in an object database they must be reconstituted. This does not mean a new object is create. There is a new instance but conceptually the object is the same.

  • Figure 6.22 and 6.23

    • The repository delegates the reconstitution to the factory (6.22)
    • The newly created object the client makes is passed to the repository for storage (6.23)

Designing Objects for relational databases

  • The database is a nonobject component that must store the persistent form of objects

  • When a database acts as an object store we can accept some model limitations in order to keep the mapping simple

  • However, even though the technical relational table design of the database does not have to reflect the object model, doing so can create overlapping models, increasing mapping complexity and sacrificing the richness of the object model

  • Sometimes selective denormalization can be needed in order to not lose the tight coupling of the model and implementation

  • Another case is when the data comes from a legacy or external store of data never intended as a store for objects

    • In this case there are two domain model co existing in the same system (chp14 maintaining model integrity)
  • How to deal with these two models

    • Every table row should contain an object
    • Foreign keys should be references to other Entities
    • Ubiquitous Language can also be helpful in tying the object and relational components together, to a single model