Abstract

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.

Lessons

Preface

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.

Intro

The word architecture is a loaded term and primarily used to denote that something is important. However, there are two common elements:

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.

Performance

Guidelines:

Terms:

Often makes sense to build for scalability rather than capacity or efficiency since it is often cheaper to buy servers than dev.

Patterns

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.

1. Layering

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.

Benefits

page 17

Downsides

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.

Using 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.

3 Layers

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.

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

2. Organizing Domain Logic

Making a Choice

You can change your choice, but it is costly.

Service Layer

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.

Inheritance (45)

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.

4. Web Presentation

MVC (Model View Controller) (55)

MVC (330) is useful to separate request and response processing as well as separating the non-presentation logic from the presentation layer. Fowler prefers the term “input controller” to describe controllers in the MVC pattern and to distinguis them from application controllers. Input controllers pull information off of the request and then forwards the business logic to an appropriate model which then returns control to the input controller.

The controller then decides what and how the data should be displayed. MVC makes it easy to separate the models from the web presentation and add additional presentations later.

Application Controllers (379) can be used to separate presentation objects from domain objects. They’re usefull if you don’t have a simple mapping between pages and actions on the domain.

View Patterns (58)

There are two primary view patterns, Transform View and Template View, with one modifier (Two Step View). Template View allows you to write the presentation in the structure of the page. Transform View uses a stylesheet to transform the data (e.g., XLST and XML).

One Step View maintains a single view component for each screen in the application. Two Stage View breaks it into two steps producing the logical screen from data and rendering it in HTML. The pattern works well when there’s a need to have a different frontend for multiple consumers. Providing a variable frontend can be accomplished by different second stages.

Input Controller Patterns (61)

One pattern is Page Controller (333) where you have an almost one-to-one mapping between controller object and action. It often makes sense to separate the HTTP response handling and to decide what to do with it elsewhere. Front Controller (344) makes this easy by delegating HTTP request handling to separate objects for processing.

5. Concurrency (63)

Concurrency is difficult since it is hard to enumerate the potentijal scenarios. This makes testing even more difficult.

Transactions can help you avoid these problems but only if the interaction can fit into a single transaction. If not, then you must deal with offline concurrency which spans multiple transactions.

Concurrency Problems (64)

Last updates are where one update starts and ends before another begins but before the other ends, resulting in lost data.

An Inconsistent Read is where you read two things that are correct but not correct at the same time.

A failure of correctness is the two problems above, but the essential issue with concurrency programming is liveness which is how much concurrent activity can occur. You often have to sacrifice correctness to gain liveness depending on the seriousness of the failure.

Execution Contexts (65)

Two main perspectives:

  1. First is from the outside world which pertains to a request (a single call) and a session (multiple calls). The other is concerning the operating system which relates to processes, which are heavyweight execution. Contexts which provide significant isolation, and threads, which are lighter weight and can operate in a single process but usually share memory unless they’re isolated.

Execution Contexts are confusing since they often don’t line up well between sessions, processes, and transactions.

Isolation and Immutability (66)

To avoid concurrency problems when more than one agent has access to a piece of data, use isolation to partition the data. Also, use lock files. Concurrency design focuses on creating these isolated zones.

To avoid concurrency problems entirely for specific pieces of an application, make appropriate data immutable. Separate applications that only read data from those that write data.

Optimistic and Pessimistic Concurrency Control (67)

Optimistic Control is about conflict detection while pessimistic control is about conflict avoidance. Use optimistic when conflicts don’t happen often, or they’re not severe. Use pessimistic control when the conflicts are painful. By using either of those solutions, you introduce other problems that may be more difficult than the original set of problems.

Preventing Inconsistent Reads (68)

You detect an inconsistency problem by using a version marker with the data. Another way is to use pessimistic locks though it is often unnecessary to control access to every bit of data. You can also use temporal reads where you prefix the data with a version of some sort (timestamp or an immutable label).

Deadlocks (70)

Deadlocks occur when two parties attempt to edit the same file when it is already locked by one of them. A few techniques to handle this are: detecting a deadlock and picking a victim, deadlock timeout, forcing everyone to acquire all their locks at the beginning, or a combination of these strategies. Prefer simple and conservative schemes for enterprise applications.

Transactions: ACID (71)

Software transactions are described in terms of ACID:

Transactional Resources (72)

A transactional resource is any resource that is transactional but is most commonly a database. You should not allow transactions to span multiple requests in most instances. A request transaction is a better model. Late Transaction is where you do all the reads outside of the transaction and the writes inside. Generally, it is not worth it. Lock isolation can have a severe effect on concurrency.

Reducing Transaction Isolation for Liveness (73)

There are four levels of isolation: the strongest level is serializable. It ensures correctness but sacrifices throughput and liveness. To increase both of those, you often have to reduce isolation.

The first measure of doing so is Repeatable Read. It introduces the potential for phantoms (where items are added to a collection, but the reader only sees some of them).

Next down is Read Committed which allows unrepeatable reads. It is easier for DBs to spot unrepeatable reads than phantoms.

The lowest level is Read Uncommitted. It allows dirty reads where you can read data that another transaction hasn’t committed yet.

Business and System Transactions (74)

System transactions are those transactions such as the ones supported by an RDBMS though they don’t have any meaning for the user.

Business transactions relate to end user use cases such as an ATM transaction. They can span multiple requests or be in one transaction though they are often run in one session. The application must provide the glue between the requests. There’s usually a problem with isolation, clashing updates, inconsistent reads, etc. Unit of Work is an excellent pattern to solve these problems. Atomicity and Durability are usually easily supported by business transactions.

Application Server Concurrency (78)

Handling application server requests poses different challenges than persistence layer concurrency problems since you don’t have transactions to fall back on. Process per request and process per session offer almost complete isolation and robustness. The former is the preferred route especially if the team is less-experienced or if there aren’t any performance benchmarks.

Thread per request is helpful if you have extreme performance needs but should be avoided in most scenarios given the opportunity for inconsistencies. You often should worry about object creation performance considerations when considering concurrency solutions since the performance hit is usually not a large enough concern to worry about.

6. Session State

Statelessness (81)

Statelessness implies not persisting objects between requests. Statelessness can bring about disaster. Statelessness allows objects to be pooled and thus more requests can be handled.

Session State (83)

Operating within a business transaction and follows many of the same rules. This is distinct from record data which is persistenced. Consistency is a considerations when working with session state.

Ways to Store Session State (84)

Client session state stores the data on the client and is only useful if the payloads are smaller since they are passed with every request. It is easy to forget a session using client session. There are security risks involved.

Server session state is the easiest on development resources, and it is stored in memory.

Database session state is tricky and adds extra overhead. Avoid unless absolutely needed.

You can use multiple approaches.

Server migration is often better than server affinity.

7. Distribution (87)

Microservices are just distributed objects reinvented. A procedure call locally is very efficient whereas remote calls are slugs. Don’t distribute your objects since remote interfaces are course-grained because of the need to combine data because of the slowness of the call. Local calls use fine-grained interfaces which are work best for object-oriented design.

Where you have to distribute (90)

Places where you can separate the processes include between client and server (a natural separation), between application and database (a natural and performant choice), between the web and application server (often better to have on the same server), if there’s a vendor resource that requires it, or if you must because of the nature of the application.

Be parsimonious with object distribution and sell your favorite grandma first if you possibly can.

Working with distribution boundaries (91)

You should limit your distribution boundaries as much as possible since they are expensive. Use a Remote Facade (388) on the boundaries to provide a coarse-grained object/interface at the boundaries whose sole role is to provide a remote interface to the fine-grained objects. Acts as a facade for the fine-grained objects.

Data Transfer Objects (401) exist so that you can transfer course-grained objects. Don’t reference anything other than Data Transfer Objects and scalar types.

Interfaces for Distribution (92)

The best of both worlds solution is to translate HTTP calls into an object-oriented structure. But this adds complexity. It can be better to make remote procure calls in some situations.

8. Putting it all together (95)

Domain Layer (96)

You should strive to choose tools based on architecture, but in practice, you’ll often have to match your architecture to your tools. One of the Domain Model’s faults is the difficulty of how to use it properly and how messy it is to handle database connections.

Data Source Layer (97)

Domain Model: Active Record (160) is good for up to a couple of dozen classes but after that Data Mapper is better. However, it requires the greatest team skill.

Presentation Layer (99)

This layer should be independent of the others and they should allow for it to be swapped out. MVC is good for a Domain model. How you communicate with the other layers depends on the kind of layer that they are. It is easier if they are in the same process.

Stored Procedures (102)

Stored procedures are not a suitable structuring mechanism and are susceptible to vendor lock-in. Avoid them unless there’s a big performance benefit.

Web Services (103)

Don’t try to break up an application into multiple web services (microservices) unless you really need to. Build your application and expose parts of it as web services. Don’t forget about the first law of distributed object design (pg 89).

Other Layering Schemes (103)

When working with mediating layers, Martin Fowler likes to start with the three base layers and add mediating layers if things get too complicated. Martin describes mediating layers as patterns.

Patterns

Transaction Script (111)

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.

Repository (322)

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.

Registry (480)

A well-known object that other objects can use to find common objects and services.

A registry is generally a global object, one per layer execution context (thread or process scope), that allows other objects to find common objects and services. You could use a map for the same purpose, but a class allows for explicit methods and type checking. Singletons may be used as an implementation.

You must think about registries in terms of interface and implementation. Fowlers preferred interface is static methods. He doesn’t recommend using a singleton if there is mutable data at play. Typically, according to Fowler, you should only use a registry as a last resort. However, more localized and non-global registries (to find a class for a particular class type) most likely would not fall under this restriction.

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.