Patterns to assist with the creation of enterprise software. Enterprise software can be small to large applications, but the use of good patterns can assist with development, maintenance, longevity, and cost-efficiency.
Designing and developing systems of objects to make them more tractable is exciting. The primary benefit of objects is that they have the opportunity to make complex logic tractable. Iterative development is key to building enterprise apps. Object-oriented methods and techniques assist with the development.
Enterprise applications, handle large amounts of data, business processes, and manipulation of data. They are different from embedded systems, telecom, or desktop productivity.
The word architecture is a loaded term and primarily used to denote that something is important. However, there are two common elements:
- High-level breakdown of a system into its parts
- Decisions that are hard to change
Other names for enterprise apps include information system and data processing. Most non-trivial enterprise apps use a layered architecture. Enterprise apps tend to deal with lots of data and complex business rules. They usually persist data that is accessed concurrently by numerous users and batch processing. They often integrate with other enterprise apps through different collaboration mechanism.
Enterprise applications can be small, however, improving them is not without benefit since improving many small apps can have a disproportionate impact on the company. One of the best things that you can do is turn a big project into a small one by simplifying its process and architecture.
Conceptual dissonance is a significant challenge for enterprise applications since fields can have different meanings in different areas of the application.
Business logic is often illogical, and it changes over time. Business logic is often just given to you, and you have little control over it. There are often strange conditions and one-offs that often interact with each other in strange ways.
Every application requires judging between different alternatives and trade-offs to form the architecture for an app based on the specific problem set.
You can’t build software without thinking
Bad decisions now can hinder future development, but the wrong addition of patterns can make it even harder.
- All advice and changes should be tracked and measured
- Minimize remote calls
- Significant change in performance may invalidate any facts about performance
- Response time: time as viewed by a requester to process a request
- Responsiveness: how quickly acknowledgment is received by the user from the system
- Latency: minimum time required to get any response. Why you should minimize remote calls
- Throughput: how much you can do in a given amount of time
- Performance: is either throughput or response time
- Improving responsiveness at the cost of response time or throughput can increase performance to the user
- Load: stress a system is under
- Load sensitivity: how response time varies with load
- Efficiency: performance divided by resources
- Capacity: max effective throughput or load
- Scalability: how adding resources affects performance
Often makes sense to build for scalability rather than capacity or efficiency since it is often cheaper to buy servers than dev.
Patterns create a vocabulary about design which aids communication. Every pattern needs to be adapted to your circumstances. Patterns are independent but may only be applicable if others are present.
Definition of a patter pg 9 by Alexander.
Lower layers don’t know about the higher layers. Tier implies a physical separation whereas the term “layers” does not. Layers could be on the same or different systems.
- Layers are coherent wholes
- Are substitutable
- Dependencies are minimized
- Good place for standardization
- Cascading changes are not encapsulated well
- But they can be handled. However, additions will still require a modification in every layer
- Modifications, however, can be handled through inversion of control
Evolution of layering
Client-server systems created the concept of a direct connection between the GUI and database. This connection has reemerged in our frontend frameworks (Meteor) but also in backend frameworks. In Laravel, you can store the data directly from the controller, and you can pass the request down as far as you like. However, this creates a dependency on the frontend in the backend. It should be broken up into layers.
By working with the data in the format of the view (whetHer the request or the response), you are coupling yourself to that view layer. Response building and request decomposition in domain logic is considered bad practice. Convert the data from/to the desired view.
When you are working with views in logic, you are not allowing the logic to be appropriately structured with objects. The view for an API is the JSON or whatever the response structure is that is being built up.
By using a 3-layer system, you can move the domain logic out of the view and structure it properly with objects. Using a 3-layer system when not necessary creates needless complexity.
A 3-layer system allows for the swapping out of the presentation logic.
Data source logic communicates with other systems that carry out tasks on behalf of the app (e.g., database). The presentation layer handles interactions with the users.
The data source layer provides data to the domain layer which processes the data and then hands that data back to the presentation layer to be further processed and formatted and then presented to the user. Formatting of data for presentation should not be happening in the domain logic.
The presentation layer is where API requests and responses happen, and it’s where controllers live. Models reside in the data source layer. No framework components should be in the domain layer in most cases but possibly packages in certain circumstances.
An asymmetric architecture that has three principal layers: presentation, domain, and data source (originally by Brown). Asymmetric because of the provided interfaces.
The Presentation layer (controller) often accesses the data source directly even though this is less pure. An app can have multiple implementations of each layer. Users can be client programs. Alistair Cockburn’s Hexagonal architecture is a core surrounded by interfaces to external systems. Everything external is an outside interface. This is an asymmetrical view.
The presentation is an interface to others that you own. Whereas sets source is an interface to you that someone else owns. Ownership is where the asynchronous nature comes from.
If it is a simple app, you can still break things into layers by say using functions rather than classes. You still want a layered approach, but it can be more straightforward. If things are more complex, use classes. If even more complicated than that, then packages.
Dependencies: domain and data source should never be dependent on the presentation layer, which means no call from those two layers into the presentation code. Likewise, they should not be dependent on the structure of the data the calling entity must conform to the interface of the called. An ill-defined data structure such as a PHP array violates this dependency contract.
- Layers make it easier to substitute and modify the presentation layer without serious ramifications deeper in the layers.
What is domain logic? * An example is a list of products with increasing sales. The logic to determine increasing sales should be in the domain whereas the logic for display, i.e., how to highlight, should be in the presentation layer.
Where to run layers
- Simplest is running everything on the servers
- Data source almost always runs on the server
- Running on the client may be required by responsiveness or disconnected operation
- Web client if you can and rich client if you must
- Business logic can be either or a split
- Server best for maintenance
- Splitting worse
2. Organizing Domain Logic
- Three principal methods
- Transaction script
- Simple procedural model
- Disadvantages appear as complexity increases
- Can result in a tangled web without a clear structure
- Essentially a function that is invoked and ran procedurally
- Domain model
- Handles increasingly complex logic well and organizes it
- Forwards behavior until strategy object creates the results
- Takes longer to learn
- Table Module
- Hybrid: one contract instance for each contract in the DB
- Organize domain logic around tables
- More structure than Transaction script
- Transaction script
Making a Choice
You can change your choice, but it is costly.
You can split the Domain Layer into two by using the Service Layer pattern so that the Presentation can only interact with the Domain logic through the Service Layer. Fowler prefers the minimal Service Layer (a Facade really)—if you need it—but Randy Stafford prefers a rich Service Layer and has been quite successful with it.
For Transactions Scripts, most of the logic would be put in the Service Layer.
Another method is the controller-entity style, which is different than the MVC style controller. Where the logic particular to a single Transaction or Use Case in a Transaction Script. It’s mostly a use case controller. However, it tends to encourage duplicate code.
Fowler recommends not having a fixed layer if service objects that contain business logic.
3. Mapping to Relational Databases
There are many downsides to SQL—and OO Databases are much more productive—however, there is significant backing for them.
It is best to separate SQL access from the domain logic generally using one of the Gateway patterns even for small applications (even with Data Mapper). However, Gateway isn’t enough when working with Domain Model you should use either Active Record, when things are relatively simple, or Data Mapper when you need complete isolation of the two layers.
Active Record makes things more difficult to test since it knows how to persist itself.
The Behavioral Problem
Most people think of the structural problems when they look at Object Relational Mapping. However, the behavioral aspects are the most challenging. Coordinating object persistence and retrieval is challenging. So that you do not run into concurrency issues relating to invalid or inconsistent data for larger apps, you must coordinate the DB reads and writes. Unit of Work is one pattern that helps solve this by abstracting and controlling the rights and reads to the DB.
Another problem commonly encountered when using a Domain Model is loading in too much data because of the linked nature of objects. An excellent pattern to handle this is Lazy Load which reduces what you bring back by using references to data via IDs or another function.
Reading in Data (40)
Usually best to read data in at the beginning so that in-memory data won’t be missed. Almost always better to pull back too much data, using joins and the like, than too little. Most DBs are optimized for 3 or 4 joins per query before the DB performance starts to degrade.
Structural Mapping Patterns (41)
When people talk about Object Relational Mapping, they’re really talking about mapping between in-memory objects and database tables. Because of database normalization, it leads to a reversal of the data structure between objects and the tables since an item belonging to an order must have a reference to the order.
Use an Identity Field (216) to solve the object representation problem. Use Foreign key mapping (236) to map a single value field. Also, use Association Table Mapping (248) to map many-to-many relationships.
A common gotcha is to rely on the ordering within collections when saving to the DB since it is difficult to maintain an arbitrarily ordered collection when saved.
Referential integrity can make updates complex since you need to defer checking to the end or be careful with your save order.
Hierarchic data structures can be saved as a Serialized LOB (302). It is a good strategy when there are many small, linked objects. It should only be used for isolated objects and when you don’t want to query for parts of the stored structure.
Mapping of inheritance can be handled using three methods or a combination: Single Table Inheritance (278), which is the simplest to implement and refactor through its size can become a problem. Class Table Inheritance is the simplest, but it needs several joins. Concrete Table Inheritance avoids the joins, but it is brittle to change.
Cope with multiple inheritance by using variations of the trio of patterns.
Building the Mapping (47)
There are three situations that you encounter when building a mapping layer: greenfield design, existing and rigid, and existing but negotiable. The simplest case is greenfield where you can design your DB structure around your object structure.
Double Mapping (48)
Double Mapping should be used when data needs to be pulled from more than one source. The simplest option is multiple mapping layers. If the data is similar, a two-step process can be better: one to maximize similarities and the other to map to the differences in the DB (use a Gateway (466) for this step).
Using Metadata (49)
You can tackle repetitive code through OO utilities, but sometimes it is better and more efficient to use a more sophisticated approach such as Metadata Mapping (306). A Query Object (316) can help you accomplish this through building queries in terms of in-memory objects. Repositories can be used to hide the DB from the Domain layer.
Database Connections (50)
Connections are expensive, generally, to create and it’s necessary to destroy and or release them when you’re done with them. You can pass a connection around as a parameter, but it’s often better to use a Registry. An excellent way to handle them is to tie them to a transaction. Unit of Work can be helpful here to manage the transaction and the connection.
Transaction Script (111)
- Organizes logic by procedures where each procedure handles a request from the presentation.
- Domain logic is organized by transaction
- Separate transactions by subject classes or the command pattern for a single class. You can use global functions, but instantiating objects helps with threading issues.
- Benefit is simplicity. When business logic gets more complicated, then it breaks down. Common code tends to be duplicated.
Domain Model (116)
A Domain Model is what most people are referring to when they talk about Object-oriented development. It’s vague.
A rich Domain Model will look significantly different than the database and would benefit from the Data Mapper pattern for accessing the Data Source layer.
There should be a minimum of coupling from the domain model to other layers. Don’t separate usage specific behavior; put it in the object that is a natural fit and then fix bloating if it becomes a problem.
When to use this pattern is hard, but it comes down to complexity: how comfortable the dev team is.
Complaint about hunting around OO code to find what you are working on is answered by the avoidance of duplication and coupling (pg 121).
Service Layer (133)
The Service Layer pattern organizes business logic into two sub-types: Domain Logic and Application Logic. When using the domain facade implementation, the service classes are a thin layer and don’t implement any logic since they delegate to the domain model.
In the operation script approach, the Service Layer implements application logic but delegates to Domain Logic classes. Often organized as scripts several to a class definition and should extend a supertype.
Don’t underestimate the cost of a remote invocation of a service layer. Start locally and remote ability if needed. User interface and use cases define the service operation. Most of the operations in an enterprise application are CRUD and usually correspond one-to-one with service operations, but additional coordination and notification responsibilities add complexity.
Usually, small applications work well with one service abstraction whereas larger apps are partitioned into sub-systems. Don’t use it when there is only one kind of service client (a GUI). The controller can generally coordinate in this case.
Active Record (160)
Active Record carries both data and behavior in a manner that matches the record structure of the underlying DB; thus it is an isomorphic schema. One disadvantage is that it couples object design to DB design. You can also implement some pieces of business logic in an Active Record class.
It doesn’t hide the fact that a relational DB is present though it is a good choice for domain logic that isn’t too complex. If it is complicated, you’ll want to use Data Mapper. This pattern is similar to Row Data Gateway except that RDG only contains DB access logic.
Data Mapper (165)
Good to use when there is a lot of business logic. This pattern abstracts the business logic from the DB. Its responsibility is to transfer data between the two and to isolate them from each other. The mapper isn’t even known by the domain layer. Different strategies and patterns have to be employed to handle classes that turn into multiple tables and or relations.
Needs to understand what has changed in an object which can be accomplished using the unit of work pattern. You can use reflection coupled with a rich constructor or use an empty object by itself. Meta mapping is useful but not as explicit. Using this pattern with Lazy Load is effective while Separated Interface might be more reasonable for larger apps.
The price of this pattern is an extra layer. Test for usage is the complexity of business logic. It is best to separate the DB layer.
Unit of Work (184)
Unit of work coordinates the writing and dirty checking (changed) of objects that need be persisted to a storage platform. To do this, it abstracts the update logic and sometimes the read logic depending on the particular implementation. Three common implementations are Caller Registration where the caller has to register with the Unit of Work, Object Registration where loading an object registers it as clean with the Unit of Work (typically done via registration calls in DB methods). Unit of Work controller manages reads and writes to the DB; therefore, it handles the registration of dirty objects and composes the object at write time.
Unit of Work is helpful when there are relational constraints with the data when batching of SQL calls would be helpful. Unit of Work can be used with any transactional resource. Unit of Work is a good use for business transactions that span multiple sessions. There are alternatives to Unit of Work for less complex situations.
Identity Map (195)
Ensures that database rows aren’t loaded more than once therefore eliminating issues when it comes to writing back to the DB. There’s a tertiary benefit of a performance gain as well. Usually you will have one per table with an isomorphic schema though if all objects all belong to some inheritance tree then one map for the whole tree often works best though maintaininng a map becomes harder because of possible index collisions.
Generic maps allow for reuse of methods and a registry if you use that pattern as well. Can use it if all objects have the same kind of key. Explicit maps gives you compile time checking and an explicit interface. You technically don’t need a map for immutable objects.
Lazy Load (200)
The Lazy Load strategy allows you to defer loading of fields until they are needed. This strategy helps combat the problem where loading an object with all of its association brings back way too much data. To implement the Lazy Load, you will need to ensure that all calls to a field go through a getter or setter meaning that the field is self-encapsulated. Fowler recommends not using this pattern unless you believe you need it.
Dependent Mapping (262)
Dependent Mapping is a strategy to simplify the mapping of database relations. In this scheme, an object can only have one owner though dependents can own another dependent. However, there is only ever one primary owner. No other object other than the owner should reference a dependent. These relationships are established by using composition.
In many ways, dependents are like Value Objects. Tracking is easier if dependents are immutable. Avoid large graphs of dependents. It is not recommended to use Dependent Mapping if you’re using Unit of Work.
Mediates between the domain and mapping layers to create an abstraction between the domain and data layer. The repository pattern uses the Specification pattern to define what should be retrieved. The repository returns domain objects and not data that represents the tables.
This pattern uses Metadata mapping with Query Object to automatically generate SQL.
The Data/Object source can be an API, a test DB, or any number of data sources since the data mapping can be swapped out using the Strategy pattern. Immutable domain objects can be kept in memory using this pattern. This pattern should be used when it is a large system, and there are many queries. It reduces the code necessary for the queries.
This pattern also works well when working with multiple data sources.
Data Transfer Object (401)
DTOs carry data between different layers or remote systems. They are used to aggregate data so that there are few total requests.
DTOs are little more than fields with getters and setter and a serialization mechanism. One strategy to construct DTOs is an assembler object.
In the traditional implementation, DTOs can couple both systems to each other and the language. Use as a common source of data for multiple layers. Shy away from arrays/dictionaries for DTOs. DTOs shouldn’t be dependent on Domain Objects—independent of external interfaces.
Make the generator only as complicated as you actually need it to be, and don’t try to put in features you only think you’ll need.
Separated Interface (476)
Separated Interface is a strategy to break dependencies between packages and allow for greater decoupling. Typically the interface is in the client package because the client defines the interface, or in a separate package from the client and implementation (better for multiple clients). Doing so helps in situations where the client may need to invoke methods that contradict the dependency structure (e.g., domain calling data source).
One awkward thing is how to instantiate the implementation. Factories, Plugin (499), and a Dependency Injection container are ways to do so.
Separate interfaces for every class is excessive especially for application development since it is extra work. Use a separate interface if you want to break a dependency or if you want to have multiple independent implementations. Separating an interface from the implementation is often a simple refactoring.
Service Stub (504)
Service Stubs are used to allow testing a remote service locally by swapping out the objects at runtime. To do so, you should use Separated Interface along with Gateway. The Stub should only be a few lines of code even in a dynamic implementation. Test helper methods may need to be added to the Gateway interface though they should throw when not under test. You can replace the Service Stub with a Mock Object though as of this writing Fowler preferred the Service Stub because it had been around longer.