Upload
ngonhi
View
217
Download
4
Embed Size (px)
Citation preview
Software Architecture DesignArchitectural Patterns
Matthew Dailey
Computer Science and Information ManagementAsian Institute of Technology
Matthew Dailey (CSIM-AIT) Patterns 1 / 193
Readings
Readings for these lecture notes:
- Fowler (2002), Patterns of Enterprise Application Architecture,Addison-Wesley, Ch. 1–3, 9–11.
Some material c© Fowler (2002).
Matthew Dailey (CSIM-AIT) Patterns 2 / 193
Outline
1 Introduction
2 Domain logic patterns
3 Mapping to relational databases
4 Web presentation
5 Concurrency
6 Session state
7 Distribution strategies
Matthew Dailey (CSIM-AIT) Patterns 3 / 193
Introduction
Fowler (2002) opens with a quote from Christopher Alexander:
Each pattern describes a problem which occurs over and overagain in our environment, then describes the core of the solutionto that problem, in such a way that you can use this solution amillion times over, without ever doing it the same way twice.
Alexander was actually a (building) architect, but the same idea is relevantin software system architecture. Patterns represent best practices we canapply to ensure we don’t make the same mistakes the pioneers did.
Matthew Dailey (CSIM-AIT) Patterns 4 / 193
Introduction
In software architecture as in building architecture, patterns are referencesforming a common vocabulary for design.
Patterns are not recipes! You can never blindly apply them, since they arealways incomplete to some degree.
Matthew Dailey (CSIM-AIT) Patterns 5 / 193
Introduction
We begin with Fowler’s (2002) patterns, which share a common structure:
Each pattern has a carefully chosen name.
Each pattern has an intent summing up the pattern in a sentence ortwo.
Each pattern has a sketch representing it visually, in UML orotherwise.
Each pattern has a motivating problem as an example of what kind ofproblems this pattern solves.
Each pattern has a detailed how it works section including adiscussion of implementation issues and variations on the theme.
Each pattern has a when to use it section describing when it shouldbe used. There will often be multiple patterns applicable in aparticular situation.
There may be further reading for more information.
Example code gives the idea of how to apply the pattern in Java orC#. The code is not to be plugged in but adapted to the situation.
Matthew Dailey (CSIM-AIT) Patterns 6 / 193
Outline
1 Introduction
2 Domain logic patterns
3 Mapping to relational databases
4 Web presentation
5 Concurrency
6 Session state
7 Distribution strategies
Matthew Dailey (CSIM-AIT) Patterns 7 / 193
Domain logic patterns
Within the domain logic or business logic layer, how can we organize thelogic?
There are three main patterns:
Transaction Scripts
Domain Model
Table Module
We might also add a Service Layer on top of the domain model.
We consider the basic idea of each pattern, then the details.
Matthew Dailey (CSIM-AIT) Patterns 8 / 193
Domain logic patternsTransaction Scripts
Transaction Script organizes business logic by procedures where eachprocedure handles a single request from the presentation.
Transaction Scripts are the simplest way to organize domain logic. ATransaction Script:
Takes input data from the presentation layer;
Processes the data with validations and calculations;
Updates the database;
Invokes operations from other systems;
Replies to the presentation layer with data to display.
We write a single procedure for each use case or transaction.
Matthew Dailey (CSIM-AIT) Patterns 9 / 193
Domain logic patternsRationale for Transaction Scripts
Most business applications are just a series of database transactions.
Each transaction contains a bit of business logic, usually simple, such asselecting what information to display, validating input, doing calculations,and so on.
With Transaction Scripts, common functionality can be broken intosubroutines, but each transaction gets its own function or method.
Matthew Dailey (CSIM-AIT) Patterns 10 / 193
Domain logic patternsStructuring Transaction Scripts
Scripts should be in classes separate from the presentation and datasource, either one class per script or multiple scripts grouped functionallyinto classes.
Example of one class per script with GoF Command pattern (Fowler 2002, Fig. 9.1).
Use transaction scripts for simple problems that don’t need fancy objectmodels.
Matthew Dailey (CSIM-AIT) Patterns 11 / 193
Domain logic patternsTransaction Scripts
Advantages:
Simplicity, with an easy-to-understand procedural model
It works very well with simple data sources using a Row Data Gatewayor a Table Data Gateway (see the database mapping patterns)
The transaction boundaries are obvious (start a transaction at thebeginning and commit the transaction at the end of each procedure).
Disadvantages:
Duplication across multiple actions eventually creates a tangle ofroutines with no structure.
Matthew Dailey (CSIM-AIT) Patterns 12 / 193
Domain logic patternsExample application
Now we’ll look at a Transaction Script implementation in Java for a simpleexample application.
When can a business book revenue? The rules are complex and depend onregulations, corporate policy, and many other factors.
One way to handle this is to model the RevenueRecogntions for everyContract we sign.
Conceptual model for revenue recognitions on a contract (Fowler 2002, Fig. 9.2)
Matthew Dailey (CSIM-AIT) Patterns 13 / 193
Domain logic patternsTransaction Script example code
Suppose we use the data model:
CREATE TABLE products (ID int primary key, name varchar, type varchar)
CREATE TABLE contracts (ID int primary key, product int,
revenue decimal, dateSigned date)
CREATE TABLE revenueRecognitions (contract int, amount decimal,
recognizedOn date,
PRIMARY KEY (contract, recognizedOn))
Matthew Dailey (CSIM-AIT) Patterns 14 / 193
Domain logic patternsTransaction Script example code
Here’s how we might map to the data source using the Table DataGateway pattern to wrap SQL queries (more on data source mappinglater):
class Gateway...
public ResultSet findRecognitionsFor(long contractID, MfDate asof)
throws SQLException {
PreparedStatement stmt = db.prepareStatement(findRecognitionsStatement);
stmt.setLong(1, contractID);
stmt.setDate(2, asof.toSqlDate());
ResultSet result = stmt.executeQuery();
return result;
}
private static final String findRecognitionsStatement =
"SELECT amount " +
"FROM revenueRecognitions " +
"WHERE contract = ? AND recognizedOn <= ?";
private Connection db;
This uses MfDate (Martin Fowler date!) and JDBC prepared statements.
Matthew Dailey (CSIM-AIT) Patterns 15 / 193
Domain logic patternsTransaction Script example code
Transaction Script to get the sum of recognitions up to some date for acontract:
class RecognitionService...
public Money recognizedRevenue(long contractNumber, MfDate asOf) {
Money result = Money.dollars(0);
try {
ResultSet rs = db.findRecognitionsFor(contractNumber, asOf);
while (rs.next()) {
result = result.add(Money.dollars(rs.getBigDecimal("amount")));
}
return result;
}catch (SQLException e) {throw new ApplicationException (e);
}
}
Matthew Dailey (CSIM-AIT) Patterns 16 / 193
Domain logic patternsTransaction Script example code
Some notes of interest in the previous TS:
db is the database gateway object.
Money is a PEAA Base Pattern.
We see iteration over a JDBC ResultSet and getBigDecimal() beingused to extract the SQL decimal field as an arbitrary-precisiondecimal number.
This script could be implemented with an SQL aggregate procedure— the point is to understand how it could be done in a TS.
Matthew Dailey (CSIM-AIT) Patterns 17 / 193
Domain logic patternsTransaction Script example code
Next, calcuateRevenueRecognitions is a TS that adds the appropriaterevenue recognitions to the database based on the product type.
class RecognitionService...
public void calculateRevenueRecognitions(long contractNumber) {
try {
ResultSet contracts = db.findContract(contractNumber);
contracts.next();
Money totalRevenue = Money.dollars(contracts.getBigDecimal("revenue"));
MfDate recognitionDate = new MfDate(contracts.getDate("dateSigned"));
String type = contracts.getString("type");
if (type.equals("S")){
Money[] allocation = totalRevenue.allocate(3);
db.insertRecognition
(contractNumber, allocation[0], recognitionDate);
db.insertRecognition
(contractNumber, allocation[1], recognitionDate.addDays(60));
db.insertRecognition
(contractNumber, allocation[2], recognitionDate.addDays(90));
Matthew Dailey (CSIM-AIT) Patterns 18 / 193
Domain logic patternsTransaction Script example code
}else if (type.equals("W")){
db.insertRecognition(contractNumber, totalRevenue, recognitionDate);
}else if (type.equals("D")) {
Money[] allocation = totalRevenue.allocate(3);
db.insertRecognition
(contractNumber, allocation[0], recognitionDate);
db.insertRecognition
(contractNumber, allocation[1], recognitionDate.addDays(30));
db.insertRecognition
(contractNumber, allocation[2], recognitionDate.addDays(60));
}
}catch (SQLException e) {throw new ApplicationException (e);
}
Some notes of interest:
allocate() is a Money method that doesn’t lose pennies.
The TS needs to know about all the possible types of products (Wordprocessors, Spreadsheets, and Databases).
Matthew Dailey (CSIM-AIT) Patterns 19 / 193
Domain logic patternsTransaction Script example code
The gateway needs appropriate finders and inserters:
class Gateway...
public ResultSet findContract (long contractID) throws SQLException{
PreparedStatement stmt = db.prepareStatement(findContractStatement);
stmt.setLong(1, contractID);
ResultSet result = stmt.executeQuery();
return result;
}
private static final String findContractStatement =
"SELECT * FROM contracts c, products p " +
"WHERE ID = ? AND c.product = p.ID";
public void insertRecognition (long contractID, Money amount, MfDate asof)
throws SQLException {
PreparedStatement stmt = db.prepareStatement(insertRecognitionStatement);
stmt.setLong(1, contractID);
stmt.setBigDecimal(2, amount.amount());
stmt.setDate(3, asof.toSqlDate());
stmt.executeUpdate();
}
private static final String insertRecognitionStatement =
"INSERT INTO revenueRecognitions VALUES (?, ?, ?)";
The RecognitionService class could be normal Java class or a session bean(one instance-one client). Obviously this gets unmaintainable as thecomplexity of the business logic increases.
Matthew Dailey (CSIM-AIT) Patterns 20 / 193
Domain logic patternsTransaction Script interaction
Fowler (2002), Fig. 2.1
Matthew Dailey (CSIM-AIT) Patterns 21 / 193
Domain logic patternsDomain model
A Domain Model is an object model of the domain that incorporates bothbehavior and data.
We use OOAD to build a model of the domain, organized around thenouns in the domain.
Domain objects normally encapsulate both data and behavior, and come intwo main kinds:
Objects mimicking the data the business deals with
Objects encapsulating the rules running the business.
Matthew Dailey (CSIM-AIT) Patterns 22 / 193
Domain logic patternsDomain model
There tend to be two kinds of domain model:
Simple models look like the database design, with one domain objectfor each database table, and can use the straightforward ActiveRecord database mapping pattern.
More complex models are better for complex logic but are harder tomap, requiring the Data Mapper pattern.
Notes of interest for more complex domain models:
Break up the logic and put it in the classes it naturally belongs to.
In J2EE, Fowler recommends POJOs (Plain Old Java Objects) for thedomain model, not entity beans as recommended by EJB folks. Thisdecouples the domain model from the implementation environment.
Read GoF for useful patterns for organizing the domain classes.
Use a Data Mapper for the data source mapping and (maybe)implement a Service Layer interface to the presentation layer.
Matthew Dailey (CSIM-AIT) Patterns 23 / 193
Domain logic patternsDomain model
Advantages:
The logic is organized, with each type of object handling its ownvalidation, calculations, and so on.
Domain models can scale well as the data source gets more complex.
Disadvantages:
It can be difficult to trace the behavior for one action
Transaction boundaries might be more difficult to determine. Youwouldn’t want one domain object starting a transaction and anothercommitting it.
According to Fowler, it takes developers a long time to get used tothe approach.
Matthew Dailey (CSIM-AIT) Patterns 24 / 193
Domain logic patternsDomain Model example code
We use the same revenue recognition system even though the logic is notreally complex enough to merit a Domain Model.
Domain Model for revenue recognition using GoF Strategy pattern (Fowler, 2002, Fig. 9.3)
Matthew Dailey (CSIM-AIT) Patterns 25 / 193
Domain logic patternsDomain Model example code
class RevenueRecognition...
private Money amount;
private MfDate date;
public RevenueRecognition(Money amount, MfDate date) {
this.amount = amount;
this.date = date;
}
public Money getAmount() {
return amount;
}
boolean isRecognizableBy(MfDate asOf) {
return asOf.after(date) || asOf.equals(date);
}
Matthew Dailey (CSIM-AIT) Patterns 26 / 193
Domain logic patternsDomain Model example code
Now, calculating the revenue recognized on a particular date involvesContract and RevenueRecognition, by iterating over the currentrevenueRecognitions.
class Contract...
private List revenueRecognitions = new ArrayList();
public Money recognizedRevenue(MfDate asOf) {
Money result = Money.dollars(0);
Iterator it = revenueRecognitions.iterator();
while (it.hasNext()) {
RevenueRecognition r = (RevenueRecognition) it.next();
if (r.isRecognizableBy(asOf))
result = result.add(r.getAmount());
}
return result;
}
The complexity is higher than the TS because multiple objects areinvolved. But associating behavior only with the objects that “need toknow” scales well and reduces coupling.
Matthew Dailey (CSIM-AIT) Patterns 27 / 193
Domain logic patternsDomain Model example code
class Contract...
private Product product;
private Money revenue;
private MfDate whenSigned;
private Long id;
public Contract(Product product, Money revenue, MfDate whenSigned) {
this.product = product;
this.revenue = revenue;
this.whenSigned = whenSigned;
}
Matthew Dailey (CSIM-AIT) Patterns 28 / 193
Domain logic patternsDomain Model example code
class Product...
private String name;
private RecognitionStrategy recognitionStrategy;
public Product(String name, RecognitionStrategy recognitionStrategy) {
this.name = name;
this.recognitionStrategy = recognitionStrategy;
}
public static Product newWordProcessor(String name) {
return new Product(name, new CompleteRecognitionStrategy());
}
public static Product newSpreadsheet(String name) {
return new Product(name, new ThreeWayRecognitionStrategy(60, 90));
}
public static Product newDatabase(String name) {
return new Product(name, new ThreeWayRecognitionStrategy(30, 60));
}
Matthew Dailey (CSIM-AIT) Patterns 29 / 193
Domain logic patternsDomain Model example code
class RecognitionStrategy...
abstract void calculateRevenueRecognitions(Contract contract);
class CompleteRecognitionStrategy...
void calculateRevenueRecognitions(Contract contract) {
contract.addRevenueRecognition(
new RevenueRecognition(contract.getRevenue(),
contract.getWhenSigned()));
}
Matthew Dailey (CSIM-AIT) Patterns 30 / 193
Domain logic patternsDomain Model example code
class ThreeWayRecognitionStrategy...
private int firstRecognitionOffset;
private int secondRecognitionOffset;
public ThreeWayRecognitionStrategy(int firstRecognitionOffset,
int secondRecognitionOffset) {
this.firstRecognitionOffset = firstRecognitionOffset;
this.secondRecognitionOffset = secondRecognitionOffset;
}
void calculateRevenueRecognitions(Contract contract) {
Money[] allocation = contract.getRevenue().allocate(3);
contract.addRevenueRecognition(
new RevenueRecognition(allocation[0], contract.getWhenSigned()));
contract.addRevenueRecognition(
new RevenueRecognition(allocation[1],
contract.getWhenSigned().addDays(firstRecognitionOffset)));
contract.addRevenueRecognition(
new RevenueRecognition(allocation[2],
contract.getWhenSigned().addDays(secondRecognitionOffset)));
}
Matthew Dailey (CSIM-AIT) Patterns 31 / 193
Domain logic patternsDomain Model example code
Here is some test code demonstrating use of the model:
class Tester...
private Product word = Product.newWordProcessor("Thinking Word");
private Product calc = Product.newSpreadsheet("Thinking Calc");
private Product db = Product.newDatabase("Thinking DB");
When we calculate recognitions on a contract, Contract doesn’t need toknow about the strategy subclasses.
class Contract...
public void calculateRecognitions() {
product.calculateRevenueRecognitions(this);
}
class Product...
void calculateRevenueRecognitions(Contract contract) {
recognitionStrategy.calculateRevenueRecognitions(contract);
}
Matthew Dailey (CSIM-AIT) Patterns 32 / 193
Domain Model example code
Some points about the example:
We see how a request gets forwarded from place to place until wereach an object qualified to handle it. This is typical for DomainModels (and in all OO systems).
The Strategy pattern is effective for refactoring procedural code fullof conditionals.
Matthew Dailey (CSIM-AIT) Patterns 33 / 193
Domain Model
Fowler (2002), Fig. 2.2
Matthew Dailey (CSIM-AIT) Patterns 34 / 193
Domain logic patternsTable Modules
A Table Module is a single instance that handles the business logic for allrows in a database table or view.
Table Modules are similar to domain classes in a domain model, but aremore closely tied to the database structure.
We have a one-to-one mapping between database tables and classes in thedomain logic layer, and clients only use one instance of the class for eachtable.
This simplifies the data source mapping at the cost of a less flexibleorganization of the domain logic.
Matthew Dailey (CSIM-AIT) Patterns 35 / 193
Domain logic patternsTable Modules
If we want to use data from a particular table as a client, we
Issue a query to the database;
Get the answer back as a Record Set;
Use the record set to instantiate a Table Module object;
Invoke operations on the Table Module object, passing an identifierwhen necessary to identify a particular row.
The Table Module might be a collection of static methods or an instance.Instances allow inheritance and initialization with a particular Record Set.
Matthew Dailey (CSIM-AIT) Patterns 36 / 193
Table Modules
Typical interaction with a Table Module (Fowler, 2002, Fig. 9.4)
Matthew Dailey (CSIM-AIT) Patterns 37 / 193
Domain logic patternsTable Modules
Layers of interaction with a Table Module (Fowler, 2002, Fig. 9.5)
Matthew Dailey (CSIM-AIT) Patterns 38 / 193
Domain logic patternsTable Modules
Advantages:
More structured than Transaction ScriptsEasier to understand the flow than with a Domain ModelLess complex data source mapping than a Domain ModelGUI environments often provide direct support for displaying RecordSets, so Table Modules in the domain logic layer provide simple,direct mappings between the presentation and data source layers,with room for additional manipulation and validation of the data as itflows through. .NET works this way.
Disadvantages:
For extremely simple applications, Transaction Scripts are easier toimplement.For complex applications, Table Modules are not as flexible as aDomain Model
For an implementation in the revenue recognition example, see text.Matthew Dailey (CSIM-AIT) Patterns 39 / 193
Domain logic patternsYour choice
The proper choice of domain logic organization depends on the complexityof the domain logic and the tool support available for a particularparadigm.
A mixture of the three patterns is often used in the same application.
Matthew Dailey (CSIM-AIT) Patterns 40 / 193
Domain logic patternsService layer
A Service Layer defines an application’s boundary with a layer of servicesthat establishes a set of available operations and coordinates theapplication’s response in each operation.
We provide an API decoupling presentation from the domain logic, makingit easy to support multiple interfaces.
Fowler, 2002, p. 133
Besides decoupling, the servicelayer can be a good place to dothings that cut across domainobjects in use cases:
Transaction control
Security
Matthew Dailey (CSIM-AIT) Patterns 41 / 193
Domain logic patternsRationale for a Service Layer
Enterprise applications often have different interfaces to the samefunctionality, for example:
A rich client GUI
An integration gateway for other applications
In these cases we might break business logic into two parts:
Domain logic, which is purely about the problem domain (e.g.revenue recognition strategies);
Application logic or workflow logic.
A Service Layer might factor some or all application logic into the servicelayer, making the domain logic layer more reusable across differentapplications.
Matthew Dailey (CSIM-AIT) Patterns 42 / 193
Domain logic patternsService layer
How much business logic to put in the service layer?
At one extreme, we have transaction scripts with a simple ActiveRecord domain model.
At the other extreme, the service layer is a facade.
More commonly, application logic is implemented by operation scriptsin the service layer and domain logic is implemented in the domainobjects.
Matthew Dailey (CSIM-AIT) Patterns 43 / 193
Domain logic patternsDesigning services
We must group the external operations into service layer classesthemselves called services, usually ending with the name Service.
Keep services coarse grained, minimizing the number of calls.
This will make it possible to allow remote access later.
To find services, start with the use case model and/or user interfacespecification. Usually there is a 1-to-1 correspondence between CRUD usecases and Service Layer operations.
For Java implementation, Fowler recommends EJB stateless session beansto implement application logic in operation scripts in the service layer,which then delegate to POJO domain objects for the domain logic.
Don’t use a service layer if you think your business logic will only have oneclient (the UI) forever, but if you might have more than one client, aservice layer is worthwhile.
Matthew Dailey (CSIM-AIT) Patterns 44 / 193
Domain logic patternsService Layer example code
First we do a POJO Service Layer.
To make it interesting, we add some application logic to the revenuerecogntion application. When revenue recognitions are calculated, we needto
Send an email to a contract administrator;
Publish a message to any other integrated applications.
Now RecognitionService extends a Layer Supertype for our service layerand use an EmailGateway and an IntegrationGateway.
Matthew Dailey (CSIM-AIT) Patterns 45 / 193
Domain logic patternsService Layer example code
POJO class diagram for a revenue recognition service (Fowler, 2002, Fig. 9.7)
Matthew Dailey (CSIM-AIT) Patterns 46 / 193
Domain logic patternsService Layer example code
Ignoring persistence, here is example code:
public class ApplicationService {
protected EmailGateway getEmailGateway() {
//return an instance of EmailGateway
}
protected IntegrationGateway getIntegrationGateway() {
//return an instance of IntegrationGateway
}
}
public interface EmailGateway {
void sendEmailMessage(String toAddress, String subject, String body);
}
public interface IntegrationGateway {
void publishRevenueRecognitionCalculation(Contract contract);
}
Matthew Dailey (CSIM-AIT) Patterns 47 / 193
Domain logic patternsService Layer example code
public class RecognitionService extends ApplicationService {
public void calculateRevenueRecognitions(long contractNumber) {
Contract contract = Contract.readForUpdate(contractNumber);
contract.calculateRecognitions();
getEmailGateway().sendEmailMessage(
contract.getAdministratorEmailAddress(),
"RE: Contract #" + contractNumber,
contract + " has had revenue recognitions calculated.");
getIntegrationGateway().publishRevenueRecognitionCalculation(contract);
}
public Money recognizedRevenue(long contractNumber, Date asOf) {
return Contract.read(contractNumber).recognizedRevenue(asOf);
}
}
We’re assuming that the contract class has methods to read contractsfrom the data source layer by ID.
Matthew Dailey (CSIM-AIT) Patterns 48 / 193
Domain logic patternsService Layer example code
We’re also ignoring transactions for now — in fact, thecalculateRevenueRecognitions() operation needs to be atomic.
EJB has built-in support for container-managed transactions. If statelesssession beans are used, the transactional methods could be declared as so,with little change to the existing methods.
Matthew Dailey (CSIM-AIT) Patterns 49 / 193
Domain logic patternsConclusion
So we’ve seen the major architectural design choices for the business logiclayer.
Thus far we’ve mostly ignored persistence mechanisms for the domainobjects.
That’s the next topic.
Matthew Dailey (CSIM-AIT) Patterns 50 / 193
Outline
1 Introduction
2 Domain logic patterns
3 Mapping to relational databases
4 Web presentation
5 Concurrency
6 Session state
7 Distribution strategies
Matthew Dailey (CSIM-AIT) Patterns 51 / 193
Mapping to relational databases
The data source layer needs to communicate with various pieces ofinfrastructure, usually including a database.
Object database technology is getting more mature:
Open source frameworks like Zope, based on an object database, areincreasing in popularity and market share
They seem to save developer time compared to RDBMS mapping byup to 1/3
But most applications are still based on RDBMS technology.
SQL is (almost) standard and well-understood by most developers
RDBMSs have the backing of the big companies
Matthew Dailey (CSIM-AIT) Patterns 52 / 193
Mapping to relational databasesBasic choices
First, you should separate the SQL from the domain logic, putting it inseparate classes.
Oftentimes we have one class per database table. The classes fromGateways to the tables. We have two Gateway patterns:
The Row Data Gateway has one instance for each row returned by aquery
The Table Data Gateway returns a Record Set (a generic datastructure provided by the development environment)
Matthew Dailey (CSIM-AIT) Patterns 53 / 193
Mapping to relational databasesRow Data Gateway
Fowler (2002), Fig. 3.1
Matthew Dailey (CSIM-AIT) Patterns 54 / 193
Mapping to relational databasesTable Data Gateway
Fowler (2002), Fig. 3.2
Matthew Dailey (CSIM-AIT) Patterns 55 / 193
Mapping to relational databasesWhen to use gateways
The Table Data Gateway
Works best when you have a Table Module for the domain logic.
Works fine when we have a views or other arbitrary queries on thedatabase.
The Row Data Gateway
Works best with a simple Domain Model where domain classescorrespond well to the database structure.
Are not so good for large applications with complex domain models,where we want to decouple the domain class structure from thedatabase structure, and we may want inheritance (especially when weapply GoF and other domain logic patterns).
Matthew Dailey (CSIM-AIT) Patterns 56 / 193
Mapping to relational databasesActive Record
For simple Domain Models where Row Data Gateways are appropriate, weoften combine the domain logic with the Row Data Gateways to obtainActive Records.
The Active Record pattern works well in MVC frameworks like Ruby onRails.
Fowler (2002), Fig. 3.3
Matthew Dailey (CSIM-AIT) Patterns 57 / 193
Mapping to relational databasesData mapper
For complex decoupled Domain Models we need a Data Mapper:
Fowler (2002), Fig. 3.4
Fowler recommends never using separate Gateways with Domain Models.He either uses Active Records or a Data Mapper.
Matthew Dailey (CSIM-AIT) Patterns 58 / 193
Mapping to relational databasesBuilding a data mapper
To implement Data Mapper you can:
Roll your own!
Use a third party object-relational (O/R) mapping package.
For Java, consider Java Persistence API (JPA) implementations likeHibernate EntityManager.
Here we discuss how to roll your own, so you can understand the genericO/R mapping solutions.
Matthew Dailey (CSIM-AIT) Patterns 59 / 193
Mapping to relational databasesBehavior: Unit of Work
How do we get objects to load and save themselves to the database?
First, we add load() and save() methods to each object. But you must
Ensure that the database and object state are consistent
Ensure that updates in one process don’t affect reads in anotherprocess.
The pattern to solve the problems is Unit of Work.
A Unit of Work object:
Acts as a controller for a transaction
Keeps track of the objects that have been loaded and modified
Ensures that updated objects are properly committed to the database
Matthew Dailey (CSIM-AIT) Patterns 60 / 193
Mapping to relational databasesBehavior: Unit of Work
Approach 1: client is responsible for registering each dirty entity:
Fowler (2002), Fig. 11.1
Matthew Dailey (CSIM-AIT) Patterns 61 / 193
Mapping to relational databasesBehavior: Unit of Work
Approach 2: entity is responsible for registering itself as dirty in eachupdate method:
Fowler (2002), Fig. 11.2
Matthew Dailey (CSIM-AIT) Patterns 62 / 193
Mapping to relational databasesBehavior: Unit of Work
Approach 3: unit of work copies each object on load and compares atcommit time to determine dirty status
Fowler (2002), Fig. 11.3
Matthew Dailey (CSIM-AIT) Patterns 63 / 193
Mapping to relational databasesBehavior: Identity Map
What happens if you read the same object twice?
You will have two in-memory objects corresponding to one databaserow.
If you make updates the copies become inconsistent.
The Identity Map pattern solves this problem by:
Keeping a record of every row that’s currently in memory
Returning a reference rather than reading the object if it is already inmemory
An Identity Map is like a database cache but it is really for correctness,not performance.
Matthew Dailey (CSIM-AIT) Patterns 64 / 193
Mapping to relational databasesBehavior: Lazy Load
When you load an object having links to to other objects, what to do? Ifwe read linked objects also, we might need too many unnecessary queries.
The Lazy Load pattern solves this problem:
When a read object has links, we point them to placeholder objects
We load the referenced object later, only when necessary.
Matthew Dailey (CSIM-AIT) Patterns 65 / 193
Mapping to relational databasesReading data
Read data in finder methods that wrap SQL select statements, e.g.,find(id) or findForCustomer(customer).
A Table Data Gateway approach lets you put finders in the sameclasses with the related insert and update methods.
For a Row Data Gateway or Data Mapper you could make the findersstatic methods, but that would couple your domain objects to thedatabase solution.
A better solution: completely separate pluggable finder objectsimplementing a “finder” interface that could be talking to anydatabase or a mock service.
Finder methods work on the database, not on objects in memory, soit’s best to do all the finding before you start any creations, updates,and deletes in memory.
Matthew Dailey (CSIM-AIT) Patterns 66 / 193
Mapping to relational databasesReading data
Other tips about reading data:
Avoid issuing multiple SQL queries to get multiple rows from thesame table!
Large applications will eventually run into database performanceissues. Then the queries have to be examined, profiled, and tuned.
Matthew Dailey (CSIM-AIT) Patterns 67 / 193
Mapping to relational databasesStructural mapping patterns
If you’re using Row Data Gateway, Active Record, or Data Mapper, youneed to know the common methods for mapping between objects andtables.
The main problem is links:
Objects use a reference or memory address (pointer)
Relational databases use a key into another table
Objects use collections for many-valued fields but databases arenormalized to be single-valued
Example: an order object would have a collection of line items that don’trefer back to the order. The database would have a table of line items,each with a foreign key reference to the containing order.
Matthew Dailey (CSIM-AIT) Patterns 68 / 193
Mapping to relational databasesStructural mapping patterns
To solve the backward reference problem:
We keep the relational identity of each object as an Identity Field
We look up the Identity Field values when needed to map back andforth between object references and relational keys.
For a read:
We use an Identity Map as a lookup table from relational keys toobjects
Then we use a Foreign Key Mapping to connect the inter-objectreference
Matthew Dailey (CSIM-AIT) Patterns 69 / 193
Mapping to relational databasesStructural mapping patterns: Foreign Key Reference
Simple example of using the Foreign Key Reference pattern for amany-to-one relationship:
Fowler (2002), Fig. 3.5
When you need an object and its key is not in the Identity Map, we do adatabase access or Lazy Load, and when an object is saved, we use itsIdentity Field to update the row with the right key.
Matthew Dailey (CSIM-AIT) Patterns 70 / 193
Mapping to relational databasesStructural mapping patterns: Foreign Key Reference
For 1-to-many, there is a collection on the “1” side in the domain modeland the reference pattern is reversed in the data model:
Fowler (2002), Fig. 3.6
On a read we issue a query to find all rows linking to the ID of the sourceobject, then for each returned row we create an object and add it to thecollection (Lazy Load is also possible here).
Matthew Dailey (CSIM-AIT) Patterns 71 / 193
Mapping to relational databasesStructural mapping patterns: Association Table Mapping
For many-to-many relationships, we know we need a separate DB table forthe mapping:
Fowler (2002), Fig. 3.7
This is the Association Table Mapping pattern.
Matthew Dailey (CSIM-AIT) Patterns 72 / 193
Mapping to relational databasesStructural mapping patterns: More tips
More O/R mapping tips:
OO languages normally use arrays or lists, but relations are unordered.So it’s best to use unordered collections (sets) in the domain layerwhenever possible.
Small objects like DateRange and Money don’t need to be stored intheir own tables. In the Value Objects pattern, when reading anobject containing a small object, we get the fields of the small objectdirectly from the row for the containing object, and store them in thecontaining object as an Embedded Value. When writing, we just getthe relevant fields from the Value Object and put them directly intothe table.
This approach can be taken further by storing complex objectnetworks as LOBs (Large Objects) if we don’t care about structuredqueries or in XML if we do.
Matthew Dailey (CSIM-AIT) Patterns 73 / 193
Mapping to relational databasesStructural mapping patterns: Inheritance
For inheritance, there are 3 options:
One table for all classes in the hierarchy (Single Table Inheritance)
One table per concrete class in the hierarchy (Concrete TableInheritance)
One table for each class (Class Table Inheritance).
Matthew Dailey (CSIM-AIT) Patterns 74 / 193
Mapping to relational databasesStructural mapping patterns: Inheritance
Single Table Inheritance is convenient but wastes space and could createhuge tables:
Fowler (2002), Fig. 3.8
Matthew Dailey (CSIM-AIT) Patterns 75 / 193
Mapping to relational databasesStructural mapping patterns: Inheritance
Concrete Table Inheritance avoids waste of space but is brittle to change:
Fowler (2002), Fig. 3.9
Matthew Dailey (CSIM-AIT) Patterns 76 / 193
Mapping to relational databasesStructural mapping patterns: Inheritance
Class Table Inheritance needs joins for a single object, but is a simplemapping:
Fowler (2002), Fig. 3.10
Matthew Dailey (CSIM-AIT) Patterns 77 / 193
Mapping to relational databasesStructural mapping patterns: Inheritance
The inheritance patterns can be mixed even within one hierarchy.
You might start with Single Table Inheritance but start to refactor whenperformance or wasted space becomes a problem.
There are variations for multiple inheritance or interfaces.
Matthew Dailey (CSIM-AIT) Patterns 78 / 193
Mapping to relational databasesBuilding the mapping
When you begin building your O/R mapper, you’ll encounter one of threecases:
You get to choose the DB schema
You have to map to an existing schema and aren’t allowed to changethat schema
You have to map to an existing schema, but changes might bepossible
Matthew Dailey (CSIM-AIT) Patterns 79 / 193
Mapping to relational databasesBuilding the mapping
Some tips for case 1 (you choose the DB schema):
Avoid making your domain model look like the database — build itwithout worrying about the DB, so you can focus on keeping thedomain logic simple.
If it turns out that the domain model looks like a database design,then you can use Active Record.
Build the database gradually, with each iteration, no more than 6weeks in length, and preferably shorter.
Design the Domain Model pieces first, then worry about thedatabase, but do map to the database on every iteration.
Matthew Dailey (CSIM-AIT) Patterns 80 / 193
Mapping to relational databasesUsing metadata
For large applications with a Data Mapper you’ll end up repeating a lot ofcode.
Eventually you might refactor with Metadata Mapping. In this pattern wedescribe the mapping between object fields and database columns.Example:
<field name="customer" targetClass="Customer" dbColumn="custID"
targetTable="customers" lowerBound="1" upperBound ="999999"
setter="loadCustomer" />
If you go far enough in using metadata to abstract the database, you havea Repository using Query Objects in which developers don’t know or carewhether their data is coming from the database or memory or both. Themetadata approach is what’s used by generic object-relational mappingsystems.
Matthew Dailey (CSIM-AIT) Patterns 81 / 193
Mapping to relational databasesDatabase connections
Some tips on dealing with database connections:
Use connection pooling. Try to explicitly close connections whenyou’re done with them, or they might sit around forever or stay openuntil the garbage collector collects them.The best way to manage a connection is inside a Unit of Work — getthe database connection when you start the transaction and release itwhen you commit or roll back.For quick things like read-only access to data outside a transaction,you can open a new connection for each command and close itimmediately.
Other points:
Don’t use select *Use column names not numeric indices to reference columns in aRecord SetUse precompiled SQL queries then supply parametersGet optimization help from the project’s database expert
Matthew Dailey (CSIM-AIT) Patterns 82 / 193
Mapping to relational databasesMore on Active Record
Active Record is an object that wraps a row in a database table or view,encapsulates the database access, and adds domain logic on that data.
Typical methods:
Construct an instance of Active Record from a SQL result row set
Construct a new instance for later insertion into the table
Static finder methods to wrap commonly-used SQL queries and returnActive Record objects (separating finders into another class is goodfor testing, however)
Update the database and insert into it the data in the Active Record
Get and set the fields (these might transform SQL data types to morereasonable types)
Implement some pieces of the business logic
Matthew Dailey (CSIM-AIT) Patterns 83 / 193
Mapping to relational databasesMore on Active Record
Example Active Record code for a Person class:
class Person...
private String lastName;
private String firstName;
private int numberOfDependents;
An ID field goes in the superclass, and the database has the samestructure:
create table people (ID int primary key, lastname varchar,
firstname varchar, number_of_dependents int)
Loading an object requires finding it first (in our case with a staticmethod)...
Matthew Dailey (CSIM-AIT) Patterns 84 / 193
Mapping to relational databasesMore on Active Record
class Person...
private final static String findStatementString =
"SELECT id, lastname, firstname, number_of_dependents" +
" FROM people" +
" WHERE id = ?";
public static Person find(Long id) {
Person result = (Person) Registry.getPerson(id);
if (result != null) return result;
PreparedStatement findStatement = null;
ResultSet rs = null;
try {
findStatement = DB.prepare(findStatementString);
findStatement.setLong(1, id.longValue());
rs = findStatement.executeQuery();
rs.next();
result = load(rs);
return result;
} catch (SQLException e) {
throw new ApplicationException(e);
} finally {
DB.cleanUp(findStatement, rs);
}
}Matthew Dailey (CSIM-AIT) Patterns 85 / 193
Mapping to relational databasesMore on Active Record
public static Person find(long id) {
return find(new Long(id));
}
public static Person load(ResultSet rs) throws SQLException {
Long id = new Long(rs.getLong(1));
Person result = (Person) Registry.getPerson(id);
if (result != null) return result;
String lastNameArg = rs.getString(2);
String firstNameArg = rs.getString(3);
int numDependentsArg = rs.getInt(4);
result = new Person(id, lastNameArg, firstNameArg, numDependentsArg);
Registry.addPerson(result);
return result;
}
Note that we use an Identity Map (the Registry class in the code) to makesure we don’t load the same object twice.
Matthew Dailey (CSIM-AIT) Patterns 86 / 193
Mapping to relational databasesMore on Active Record
Updating an object is a simple instance method:
class Person...
private final static String updateStatementString =
"UPDATE people" +
" set lastname = ?, firstname = ?, number_of_dependents = ?" +
" where id = ?";
public void update() {
PreparedStatement updateStatement = null;
try {
updateStatement = DB.prepare(updateStatementString);
updateStatement.setString(1, lastName);
updateStatement.setString(2, firstName);
updateStatement.setInt(3, numberOfDependents);
updateStatement.setInt(4, getID().intValue());
updateStatement.execute();
} catch (Exception e) {
throw new ApplicationException(e);
} finally {
DB.cleanUp(updateStatement);
}
}Matthew Dailey (CSIM-AIT) Patterns 87 / 193
Mapping to relational databasesMore on Active Record
Insertion is also straightforward:
class Person...
private final static String insertStatementString =
"INSERT INTO people VALUES (?, ?, ?, ?)";
public Long insert() {
PreparedStatement insertStatement = null;
try {
insertStatement = DB.prepare(insertStatementString);
setID(findNextDatabaseId());
insertStatement.setInt(1, getID().intValue());
insertStatement.setString(2, lastName);
insertStatement.setString(3, firstName);
insertStatement.setInt(4, numberOfDependents);
insertStatement.execute();
Registry.addPerson(this);
return getID();
} catch (Exception e) {
throw new ApplicationException(e);
} finally {
DB.cleanUp(insertStatement);
}
}Matthew Dailey (CSIM-AIT) Patterns 88 / 193
Mapping to relational databasesMore on Active Record
Finally the business logic goes right in the Active Record class:
class Person...
public Money getExemption() {
Money baseExemption = Money.dollars(1500);
Money dependentExemption = Money.dollars(750);
return baseExemption.add(dependentExemption.multiply(this.getNumberOfDependents()));
}
Matthew Dailey (CSIM-AIT) Patterns 89 / 193
Mapping to relational databasesMore on Data Mapper
A Data Mapper is a layer of Mappers that moves data between objectsand a database while keeping them independent of each other and themapper itself.
The Data Mapper pattern becomes necessary when you want persistentobjects that contain collections, use inheritance, and so on.
The Data Mapper is a layer completely separate from the domain objects;the domain objects don’t need to know about the database schema orSQL.
We consider a simple example in which the Data Mapper is not reallynecessary, just to get the idea.
Matthew Dailey (CSIM-AIT) Patterns 90 / 193
Mapping to relational databasesMore on Data Mapper
A simple version of a load with Data Mapper using an Identity Map:
Fowler (2002), Fig. 10.3
Matthew Dailey (CSIM-AIT) Patterns 91 / 193
Mapping to relational databasesMore on Data Mapper
A simple version of an update with Data Mapper:
Fowler (2002), Fig. 10.4
Matthew Dailey (CSIM-AIT) Patterns 92 / 193
Mapping to relational databasesMore on Data Mapper
In the load example, there was just one SQL query, but in reality a loadwill cause several interconnected objects to be loaded using more than onequery.
Simple example: loading an order. We would probably execute a secondquery to load the line items for an order after the order data is loaded.
Matthew Dailey (CSIM-AIT) Patterns 93 / 193
Mapping to relational databasesMore on Data Mapper
Another issue is the mapping of the domain fields. The mapper will be ina different class and different package from the domain object.
For save(), we need special public getters for all fields that return the rawattributes. These should only be called in the context of a databaseoperation.
Similarly, for load(), we need a rich constructor that sets all fields, orspecial public setters for all fields.
Matthew Dailey (CSIM-AIT) Patterns 94 / 193
Mapping to relational databasesMore on Data Mapper
As a simple example in Java, we use Person again, noting that a DataMapper is not really necessary for such a simple case:
class Person...
private String lastName;
private String firstName;
private int numberOfDependents;
The database schema is the same as before:
create table people (ID int primary key, lastname varchar,
firstname varchar, number_of_dependents int)
Matthew Dailey (CSIM-AIT) Patterns 95 / 193
Mapping to relational databasesMore on Data Mapper
Person-specific code is placed in PersonMapper:
class PersonMapper...
protected String findStatement() {
return "SELECT " + COLUMNS +
" FROM people" +
" WHERE id = ?";
}
public static final String COLUMNS = " id, lastname, firstname, number_of_dependents ";
public Person find(Long id) {
return (Person) abstractFind(id);
}
public Person find(long id) {
return find(new Long(id));
}
Matthew Dailey (CSIM-AIT) Patterns 96 / 193
Mapping to relational databasesMore on Data Mapper
Code common to all mappers goes in an abstract superclass:class AbstractMapper...
protected Map loadedMap = new HashMap();
abstract protected String findStatement();
protected DomainObject abstractFind(Long id) {
DomainObject result = (DomainObject) loadedMap.get(id);
if (result != null) return result;
PreparedStatement findStatement = null;
try {
findStatement = DB.prepare(findStatement());
findStatement.setLong(1, id.longValue());
ResultSet rs = findStatement.executeQuery();
rs.next();
result = load(rs);
return result;
} catch (SQLException e) {
throw new ApplicationException(e);
} finally {
DB.cleanUp(findStatement);
}
}
Matthew Dailey (CSIM-AIT) Patterns 97 / 193
Mapping to relational databasesMore on Data Mapper
Load behavior is split between the concrete mapper and the superclass:
class AbstractMapper...
protected DomainObject load(ResultSet rs) throws SQLException {
Long id = new Long(rs.getLong(1));
if (loadedMap.containsKey(id)) return (DomainObject) loadedMap.get(id);
DomainObject result = doLoad(id, rs);
loadedMap.put(id, result);
return result;
}
abstract protected DomainObject doLoad(Long id, ResultSet rs) throws SQLException;
class PersonMapper...
protected DomainObject doLoad(Long id, ResultSet rs) throws SQLException {
String lastNameArg = rs.getString(2);
String firstNameArg = rs.getString(3);
int numDependentsArg = rs.getInt(4);
return new Person(id, lastNameArg, firstNameArg, numDependentsArg);
}
Matthew Dailey (CSIM-AIT) Patterns 98 / 193
Mapping to relational databasesMore on Data Mapper
Finding people by last name:
class PersonMapper...
private static String findLastNameStatement =
"SELECT " + COLUMNS +
" FROM people " +
" WHERE UPPER(lastname) like UPPER(?)" +
" ORDER BY lastname";
public List findByLastName(String name) {
PreparedStatement stmt = null;
ResultSet rs = null;
try {
stmt = DB.prepare(findLastNameStatement);
stmt.setString(1, name);
rs = stmt.executeQuery();
return loadAll(rs);
} catch (SQLException e) {
throw new ApplicationException(e);
} finally {
DB.cleanUp(stmt, rs);
}
}
Matthew Dailey (CSIM-AIT) Patterns 99 / 193
Mapping to relational databasesMore on Data Mapper
class AbstractMapper...
protected List loadAll(ResultSet rs) throws SQLException {
List result = new ArrayList();
while (rs.next())
result.add(load(rs));
return result;
}
Actually this method should also check the Identity Map for each personin the result set.
Matthew Dailey (CSIM-AIT) Patterns 100 / 193
Mapping to relational databasesMore on Data Mapper
Instead of duplicating the find code across every mapper, we can make ageneric finder:
class AbstractMapper...
public List findMany(StatementSource source) {
PreparedStatement stmt = null;
ResultSet rs = null;
try {
stmt = DB.prepare(source.sql());
for (int i = 0; i < source.parameters().length; i++)
stmt.setObject(i+1, source.parameters()[i]);
rs = stmt.executeQuery();
return loadAll(rs);
} catch (SQLException e) {
throw new ApplicationException(e);
} finally {
DB.cleanUp(stmt, rs);
}
}
Matthew Dailey (CSIM-AIT) Patterns 101 / 193
Mapping to relational databasesMore on Data Mapper
The generic finder requires a wrapper for the SQL query and itsparameters:
interface StatementSource...
String sql();
Object[] parameters();
The implementation of the StatementSource interface can be an innerclass (next page)...
Matthew Dailey (CSIM-AIT) Patterns 102 / 193
Mapping to relational databasesMore on Data Mapper
class PersonMapper...
public List findByLastName2(String pattern) {
return findMany(new FindByLastName(pattern));
}
static class FindByLastName implements StatementSource {
private String lastName;
public FindByLastName(String lastName) {
this.lastName = lastName;
}
public String sql() {
return
"SELECT " + COLUMNS +
" FROM people " +
" WHERE UPPER(lastname) like UPPER(?)" +
" ORDER BY lastname";
}
public Object[] parameters() {
Object[] result = {lastName};
return result;
}
}
Matthew Dailey (CSIM-AIT) Patterns 103 / 193
Mapping to relational databasesMore on Data Mapper
Updates are specific to the object we’re mapping:
class PersonMapper...
private static final String updateStatementString =
"UPDATE people " +
" SET lastname = ?, firstname = ?, number_of_dependents = ? " +
" WHERE id = ?";
public void update(Person subject) {
PreparedStatement updateStatement = null;
try {
updateStatement = DB.prepare(updateStatementString);
updateStatement.setString(1, subject.getLastName());
updateStatement.setString(2, subject.getFirstName());
updateStatement.setInt(3, subject.getNumberOfDependents());
updateStatement.setInt(4, subject.getID().intValue());
updateStatement.execute();
} catch (Exception e) {
throw new ApplicationException(e);
} finally {
DB.cleanUp(updateStatement);
}
}
Matthew Dailey (CSIM-AIT) Patterns 104 / 193
Mapping to relational databasesMore on Data Mapper
Inserts are partly generic:
class AbstractMapper...
public Long insert(DomainObject subject) {
PreparedStatement insertStatement = null;
try {
insertStatement = DB.prepare(insertStatement());
subject.setID(findNextDatabaseId());
insertStatement.setInt(1, subject.getID().intValue());
doInsert(subject, insertStatement);
insertStatement.execute();
loadedMap.put(subject.getID(), subject);
return subject.getID();
} catch (SQLException e) {
throw new ApplicationException(e);
} finally {
DB.cleanUp(insertStatement);
}
}
abstract protected String insertStatement();
abstract protected void doInsert(DomainObject subject,
PreparedStatement insertStatement)
throws SQLException;Matthew Dailey (CSIM-AIT) Patterns 105 / 193
Mapping to relational databasesMore on Data Mapper
... and partly object-specific:
class PersonMapper...
protected String insertStatement() {
return "INSERT INTO people VALUES (?, ?, ?, ?)";
}
protected void doInsert(
DomainObject abstractSubject,
PreparedStatement stmt)
throws SQLException
{
Person subject = (Person) abstractSubject;
stmt.setString(2, subject.getLastName());
stmt.setString(3, subject.getFirstName());
stmt.setInt(4, subject.getNumberOfDependents());
}
Matthew Dailey (CSIM-AIT) Patterns 106 / 193
Mapping to relational databasesMore on Lazy Load
A Lazy Load is an object that doesn’t contain all of the data you need butknows how to get it.
Lazy Load stops loading linked objects at some point in the graph, leavinga marker in the object structure.
There are four main implementation styles:
Lazy initialization
Virtual proxy
Value holder
Ghost
Matthew Dailey (CSIM-AIT) Patterns 107 / 193
Mapping to relational databasesMore on Lazy Load
The simplest Lazy Load implementation is lazy initialization:
Unloaded fields a are left as null
Every time we read a field, we first check its value, and if it is null, weload it.
This method is simple, but couples the object and the database schema.
Matthew Dailey (CSIM-AIT) Patterns 108 / 193
Mapping to relational databasesMore on Lazy Load
The virtual proxy (a GoF pattern) adds a layer of indirection:
The non-loaded object is replaced with a placeholder (the virtualproxy) that looks like the desired object but doesn’t contain any data.
When any method on the virtual proxy is called, it realizes it doesn’texist and loads itself from the database.
If identity of the virtual proxy object is important, then bookkeepingbecomes difficult. Thus it’s recommended to only use virtual proxies forcomplex value objects like collections where identity is not important.
Matthew Dailey (CSIM-AIT) Patterns 109 / 193
Mapping to relational databasesMore on Lazy Load
Another approach to Lazy Load is to use a value holder in which oneobject wraps another, which isn’t loaded until necessary.
The last approach is to use a ghost:
We create the real object itself, but only fill in the ID.
Whenever a field is accessed, we load the state of the whole object.
The ghost can be placed in the Identity Map, avoiding identityproblems.
Advanced lazy loaders load just the right subset of the object graph forthe active use case.
Matthew Dailey (CSIM-AIT) Patterns 110 / 193
Outline
1 Introduction
2 Domain logic patterns
3 Mapping to relational databases
4 Web presentation
5 Concurrency
6 Session state
7 Distribution strategies
Matthew Dailey (CSIM-AIT) Patterns 111 / 193
Web presentationUse Web interfaces!
Web-based interfaces are now almost universal for enterprise applications:
They eliminate the need for client software installations
They provide a uniform, universal, cross-platform, well-understoodframework for building UIs
They are getting easier to build as development environments improvetheir support for them
You should use a Web interface whenever you can get away with it.
With AJAX technology we can now build pretty sophisticated Webinterfaces.
Sometimes requirements will dictate a rich client instead. Consider across-platform rich client framework like Eclipse in this case.
Matthew Dailey (CSIM-AIT) Patterns 112 / 193
Web presentationScripts vs. server pages
Web interfaces operate on a request-response basis.
When the Web server receives a request, it interprets the request anddecides what program should be invoked to handle it.
Up to the mid-1990s, we only had scripts that would parse request,execute the logic, and output HTML to a stream. Perl CGI is an example.
More recent technology provides server pages in which we write pages inHTML and insert code for the dynamic parts. PHP, ASP, and JSP takethe server page approach.
Matthew Dailey (CSIM-AIT) Patterns 113 / 193
Web presentationScripts + server pages → MVC
It was clear early on that scripts are good for processing the request andserver pages are good for displaying the result.
When we combine these two ideas with the crucial separation ofnon-presentation logic from presentation logic, we get the Model ViewController (MVC).
MVC has been around in object-oriented user interfaces since the late1970s!
Matthew Dailey (CSIM-AIT) Patterns 114 / 193
Web presentationMVC
The MVC pattern splits user interface interaction into three distinct roles:
Fowler (2002), p. 330
Matthew Dailey (CSIM-AIT) Patterns 115 / 193
Web presentationMVC
The basic flow in MVC:
The controller (called an input controller by Fowler) necessaryinformation from the incoming request and forwards it to the businesslogic which is encapsulated in the model.
The model talks to the data source, transforms and validates data,and gathers all the information necessary for the response. It thenreturns the response data to the controller.
The controller (possibly with the help of an application controller)then invokes a view.
The view renders the model data for display.
MVC’s main advantage is separation of the presentation logic from themodel, making it easier to modify the presentation.
The controller-view separation is not as crucial but still useful.
Matthew Dailey (CSIM-AIT) Patterns 116 / 193
Web presentationMVC
A sequence diagram giving the essence of MVC:
Fowler (2002), Fig. 4.1
MVC is a good idea for any Web presentation. You might use a frameworklike Spring for Java, Rails for Ruby, one of dozens of PHP MVCframeworks, or roll your own.
Matthew Dailey (CSIM-AIT) Patterns 117 / 193
Web presentationApplication controllers
Web interfaces often use a layer of Application Controllers between thepresentation and model.
Application controllers are responsible for application flow (choosing whatscreen to display when).
Web presentations only need application controllers if the navigation iscomplex and under control of the system.
Matthew Dailey (CSIM-AIT) Patterns 118 / 193
Web presentationView patterns
For the view, we use either the Transform View pattern or the TemplateView pattern, either of which can be single stage or have the variationTwo-step View.
Template Views:
The presentation is written as a set of template HTML pages withmarkers indicating where the dynamic content needs to go.
Examples are ASP, JSP, and PHP.
Convenient and flexible, but difficult to maintain.
Template engines (like Smarty for PHP) help increase maintainabilityby forcing separation of business logic from the template.
Matthew Dailey (CSIM-AIT) Patterns 119 / 193
Web presentationView patterns
Transform views:
The model outputs domain data in some intermediate representationsuch as XML.
The view is a transformation program like XSLT that converts thedomain data into HTML.
Initial development takes more time than a template view, butmaintenance and testing is easier.
Matthew Dailey (CSIM-AIT) Patterns 120 / 193
Web presentationView patterns
Templates and transforms can be single stage or two-stage:
A single-stage view has one separate template or transform for eachscreen in the application.
A two-stage view creates a logical screen from the domain data instage 1 then renders the logical screen with the stage 2 for each view.
A popular approach is to use HTML for the logical screen andCascading Style Sheets (CSS) for the stage 2 rendering.
Two-stage views are useful when you have different display devices (e.g.desktop and mobile devices) to handle or when you want to easily changethe global look and feel of the application.
Matthew Dailey (CSIM-AIT) Patterns 121 / 193
Web presentationView patterns
Single-stage views:
Fowler (2002), Fig. 4.2
Matthew Dailey (CSIM-AIT) Patterns 122 / 193
Web presentationView patterns
Two-stage views:
Fowler (2002), Fig. 4.3
Matthew Dailey (CSIM-AIT) Patterns 123 / 193
Web presentationInput controller patterns
There are two main approaches for the input controller:
Page Controllers assign one controller to each page or user action.
A Front Controller handles all user actions for the application in asingle controller.
Matthew Dailey (CSIM-AIT) Patterns 124 / 193
Web presentationInput controller patterns
Page controller structure:
Fowler (2002), p. 333
Matthew Dailey (CSIM-AIT) Patterns 125 / 193
Web presentationInput controller patterns
Front controller structure:
Fowler (2002), p. 344
Matthew Dailey (CSIM-AIT) Patterns 126 / 193
Web presentationInput controller patterns
Page controllers:
See Fowler Ch. 14 for an example of a Tomcat application serverconfiguration, a servlet for a page controller, and a JSP for the view.
Also see the example combining the page contoller with the JSP view.This is common for simple page controllers that almost alwaysforward to the same view. This is also common in PHP.
Front controllers:
A front controller is good for reusing code related to security,internationalization, and per-user customization of the view.
See Ch 14 for an example servlet implementation of a front controller.
Code reuse is maximized, especially for complex interfaces.
Front controllers get big and unweildy for apps with many screens.
Matthew Dailey (CSIM-AIT) Patterns 127 / 193
Outline
1 Introduction
2 Domain logic patterns
3 Mapping to relational databases
4 Web presentation
5 Concurrency
6 Session state
7 Distribution strategies
Matthew Dailey (CSIM-AIT) Patterns 128 / 193
ConcurrencyThe issues
Concurrency is problematic because it is difficult to test.
Everything depends on the relative order of events within different threadsor processes.
In many cases, enterprise developers don’t have to worry aboutconcurrency, because our RDBMS gives us transactions.
However, when a business transaction doesn’t fit into a DB or systemtransaction, we need to worry about offline concurrency (concurrencycontrol for data manipulated over multiple system transactions).
In addition to multi-request business transactions, we sometimes have toworry about concurrency in a multi-threaded application server.
As a running example, we consider a source code control system. Thesame concepts apply to any enterprise system.
Matthew Dailey (CSIM-AIT) Patterns 129 / 193
ConcurrencyEssential problems
For enterprise applications, we have two essential problems:
Lost updates occur, for example, when 1) one user grabs a file forediting, 2) another user grabs the same file, makes an update, andsubmits the new version, then 3) the first user completes theirmodification and commits. The result is that the second user’supdate is lost.
Inconsistent reads occur when you read two correct pieces ofinformation that are not correct at the same time. For examplesuppose you’re opening a list of files, and while you’re in the middleof reading, someone else does an update to some of the files. You getsome versions from before the update and some versions from afterthe update, and these might not be consistent with each other.
Matthew Dailey (CSIM-AIT) Patterns 130 / 193
ConcurrencyEssential problems
Correctness or safety means that results should always be the same as ifthere weren’t two people working on the data at the same time.
Correctness is guaranteed by only allowing one person to work on the dataat the same time (locking everyone else out) but that reduces liveness, orhow much concurrency is allowed.
Matthew Dailey (CSIM-AIT) Patterns 131 / 193
ConcurrencyExecution contexts
Processing always occurs in some context.
One important part of the context is the particular request we are workingon.
Usually we think of requests in isolation, but they could be related. Anexample: a client places an order then cancels.
A session is an execution context tying together related requests. Asession is a long-running interaction between the client and server possiblyinvolving more than one request.
Matthew Dailey (CSIM-AIT) Patterns 132 / 193
ConcurrencyExecution contexts
A process is an execution context that has its own dedicated memoryaddress space.
A thread is an execution context in which some resources (usually thememory address space) can be shared with other execution contexts.
An isolated thread is one that doesn’t share memory with other threads(but still shares other resources, making it more efficient).
It would be nice if we could have one unique process for every session forthe lifetime of the session, but more typically we have to actively associaterequests with new processes or threads then with sessions.
Matthew Dailey (CSIM-AIT) Patterns 133 / 193
ConcurrencyExecution contexts
A transaction is an execution context containing a series of requestsshould be treated as one request.
We have transactions on both “sides” of the application server:
The user interacts with the application to form business transactions.
The application interacts with the database to form systemtransactions.
Matthew Dailey (CSIM-AIT) Patterns 134 / 193
ConcurrencyIsolation and immutability
The two methods for overcoming concurrency problems in enterpriseapplications (mainly lost updates and inconsistent reads) are isolation andimmutability:
Isolation allows a process to enter an isolated zone in whichconcurrency isn’t a problem. Conceptually, we work in a memoryaddress space separate from all other processes, and lock all externalresources.
Immutability means recognizing data that cannot be modified. Allconcurrency problems involve updates, and immutable data cannot beupdated, so immutability eliminates concurrency problems.
Matthew Dailey (CSIM-AIT) Patterns 135 / 193
ConcurrencyOptimistic and pessimistic concurrency control
When we have data that we can’t isolate, we use either optimistic orpessimistic concurrency control.
Optimistic locking is when we allow reads and writes and only generate anexception when we see a conflict.
Example: A and B check out a file at the same time. A commits first, andhis commit goes through, but when B tries to commit there will be aconflict, and B will be responsible for fixing it.
Pessimistic locking means once a resource is read, nobody else is allowedto edit it until the transaction is finished.
When A checks a file out, B can’t check out until A commits.
Optimistic locking means conflict detection; pessimistic locking meansconflict prevention.
Matthew Dailey (CSIM-AIT) Patterns 136 / 193
ConcurrencyOptimistic and pessimistic concurrency control
In source code control, for a variety of reasons, optimistic locking is thenorm (c.f. CVS and Subversion). In this case automated tools can helpresolve conflicts. For business data it’s usually too difficult and we justthrow everything out and start again.
The choice depends on how difficult conflict resolution is for users.
Matthew Dailey (CSIM-AIT) Patterns 137 / 193
ConcurrencyPreventing inconsistent reads
Solving inconsistent reads with pessimistic techniques:
To read data, we say we need a read lock.
To write data, we say we need a write lock.
Any number of clients can obtain a read lock simultaneously.
If anyone has a read lock, nobody can get a write lock.
If anyone has the write lock, nobody else is allowed any lock.
Matthew Dailey (CSIM-AIT) Patterns 138 / 193
ConcurrencyPreventing inconsistent reads
Optimistic systems use versioning to prevent inconsistent reads. Whenreading, we make sure that every piece of data read has the same versionnumber.
Related concept: temporal reads, where a timestamp on each data itemensures consistent reads.
This technique is feasible in source control, where the updates areinfrequent and the version history needs to be kept anyway.
The version history feature is almost nonexistent in databases, whereupdates are frequent and history maintenance would be too expensive.(That aside, sometimes you do need precise logging of your businesstransactions, but normally you have to design that yourself.)
Matthew Dailey (CSIM-AIT) Patterns 139 / 193
ConcurrencyDeadlocks
A deadlock occurs when there is a cyclic wait for a set of locks andnobody can proceed until they get the lock they’re waiting for.
Techniques to address a deadlock:
Detection and recovery: choose a victim and force it to roll back(difficult and drastic), or put a timeout on every lock request (makingyou a victim when your request times out — simple but more drastic).
Prevention: force all processes to get all their locks at once in thebeginning before they can continue, or put an ordering on the locksand force each process to grab them in order.
The advice is to use simple conservative schemes for enterpriseapplications, even if it causes unnecessary victimization.
Matthew Dailey (CSIM-AIT) Patterns 140 / 193
ConcurrencyACID Transactions
Our main tool is the transaction. A transaction is a bounded sequence ofwork with start points and end points well defined.
Software transactions are described in terms of ACID properties:
Atomicity (all-or-none): every step must complete successfully, or allthe steps must roll back. Partial completion is not allowed. If there’sa system crash in the middle of a bank transfer we must go back tothe original state.
Consistency: the resources must be consistent and noncorrupt at boththe beginning and end of the transaction.
Isolation: the result of a transaction must not be visibile until it hascompleted successfully.
Durability: any result of a committed transaction must be permanent,i.e., survive any kind of crash.
Matthew Dailey (CSIM-AIT) Patterns 141 / 193
ConcurrencyTransactional resources
We are most familiar with transactions in databases, but they are alsocommon in many other places. Some terms:
A transactional resource is anything using transactions to controlconcurrency. “Transactional resource” and “database” can be usedinterchangeably.
Transactions spanning multiple requests are called long transactions.
A request transaction is one started when a request is received andcompleted at the end of request processing.
Whenever possible, use request transactions, not long transactions.This maximizes throughput.
A late transaction isn’t opened until all reads are complete. Thisimproves efficiency but allows inconsistent reads.
Some development environments allow us to tag a method astransactional, so that a transaction is automatically begun at thebeginning and completed at the end.
Matthew Dailey (CSIM-AIT) Patterns 142 / 193
ConcurrencyIsolation ↔ liveness tradeoff
Oftentimes a strong isolation model leads to a system that is not very live.
Serializable isolation means the transactions can be executed concurrentlywith a result guaranteed to correspond to one of the serialized results.This is the strongest form of isolation. You can get more concurrency ifyou go to weaker models of isolation.
Repeatable read isolation means that you may see some but not all newitems being inserted while you do a read. Repeatable means you’ll alwaysget the same result, however, if you repeat the read within the sametransaction.
Matthew Dailey (CSIM-AIT) Patterns 143 / 193
ConcurrencyIsolation ↔ liveness tradeoff
Read committed means you may see some but not all of the new itemsbeing inserted, but the reads can be unrepeatable, meaning the same readperformed twice within the same transaction can return different elements.
Read uncommitted means you can see data from another transaction thathasn’t been committed yet. This is also called dirty read. If there is a rollback, we’ll see data that was never really there at all.
The advice is to always use serializable isolation unless performance is anissue, in which case you might weaken the isolation for some or alltransactions.
Matthew Dailey (CSIM-AIT) Patterns 144 / 193
ConcurrencyBusiness vs. system transactions
Usually folks talk about SYSTEM TRANSACTIONS supported by aRDBMS. When we commit, if there is a consistency failure, the partialtransaction is rolled back and the application is notified of the problem.
A BUSINESS TRANSACTION is a transaction executed by a user. E.g., abank user might log in, set up some bill payments, and click OK to pay. Abusiness transaction requires the same ACID properties as a systemtransaction.
One solution is to execute the entire business transaction within a systemtransaction, but then we would have a lot of long transactions. This issometimes OK but more often, to support ACID properties for businesstransactions we need to implement OFFLINE CONCURRENCY. We use aseries of system transactions with some GLUE between the systemtransactions.
Matthew Dailey (CSIM-AIT) Patterns 145 / 193
ConcurrencyBusiness vs. system transactions
The idea is the user begins the commit of the business transaction whenhitting SAVE or whatever. We begin a system transaction, determine thechange set using Unit of Work, do the updates, then commit the systemtransaction.
Isolation is difficult to enfore for business transactions. We have to makesure that our changes don’t step on other sessions’ changes. We also haveto ensure that inconsistent reads don’t occur.
[Think of some examples here!]
Normally we design so that every business transaction occurs in a session.Sessions then become sequences of business transactions, from the user’spoint of view.
Matthew Dailey (CSIM-AIT) Patterns 146 / 193
ConcurrencyOffline concurrency control patterns
Wherever possible, let the transaction system deal with concurrency.
When there’s a mismatch between the system transactions and businesstransactions, then you have to roll your own. The patterns include
Optimistic Offline Lock: use optimistic concurrency control across thebusiness transactions. Easiest to program and high liveness.Limitation is you don’t know the transaction will fail until you try tocommit. This can decrease user confidence in your system.
Pessimistic Offline Lock: lock everything in advance. More difficult toprogram, less liveness, earlier detection of trouble.
Coarse-Grained Lock: works with either basic approach, reducingcomplexity by managing a group of objects together rather thanindividual locks.
Implicit Lock: developers don’t have to worry about locking; theobjects do it themselves.
Matthew Dailey (CSIM-AIT) Patterns 147 / 193
ConcurrencyOptimistic offline lock
Prevents conflicts between concurrent business transactions by detecting aconflict and rolling back the transaction.
Fowler (2002), p. 416
If the chance of a conflict is pretty low, this is a good pattern forimplementing your business transactions.
The idea is during the BUSINESS TRANSACTION, the last step will be toupdate the database in a single system transaction. At the beginning ofthat system transaction, we acquire a lock on each record we’re updating,simply by checking that each one hasn’t been altered since we read it.
The lock is normally implemented by associating a version number witheach record, and incrementing that version number on each update.
If we’re using a RDBMS, we can check the version number in the samequery as the update, inspecting the version number in the WHERE clauseand checking the number of rows updated on query execution. 1 meanssuccess, 0 means failure.
Matthew Dailey (CSIM-AIT) Patterns 148 / 193
ConcurrencyPessimistic offline lock
Prevents conflicts between concurrent business transactions by allowingonly one business transaction at a time to access data.
Fowler (2002), p. 426
Matthew Dailey (CSIM-AIT) Patterns 149 / 193
ConcurrencyPessimistic offline lock
If optimistic locking is not appropriate for your application, you have toroll your own pessimistic lock.
For each resource you want to lock, that becomes a row in a table oflocks. Each lock has an owner. A lock manager is written to acquire locksand release locks. Normally you lock everything at the beginning of thebusiness transaction then release the locks at the end of the businesstransaction.
See Chapter 16 for more details.
Matthew Dailey (CSIM-AIT) Patterns 150 / 193
ConcurrencyApplication server concurrency
How do we handle simultaneous requests to the application server?Database transactions can’t help us. Hopefully the application serverenvironment will.
Process per session has each session running in its own process, isolatedfrom others. This is great but uses a lot of resources.
Pooled process per request means we allocate an existing idle process toeach new request. Session information can be saved at the end of eachrequest and restored at the beginning of the request. Isolation is good, butwe have to worry about releasing resources at the end of each request.
Thread per request means each request is handled by a single threadwithin one process. More requests with less hardware, but dangerous.
Fowler recommends process per request due to the increased isolation andequal scalability with thread per request.
Matthew Dailey (CSIM-AIT) Patterns 151 / 193
ConcurrencyApplication server concurrency
You can also get good isolation even with threads if using an environmentlike EJB that gives your threads a private playground. The thread needs toallocate all objects locally so as not to affect other threads. Avoid static orglobal variables so you don’t have to synchronize them.
For global memory, use the REGISTRY pattern, not global variables in theapplication development environment. This will scale without trouble tomultiple application servers.
Matthew Dailey (CSIM-AIT) Patterns 152 / 193
Outline
1 Introduction
2 Domain logic patterns
3 Mapping to relational databases
4 Web presentation
5 Concurrency
6 Session state
7 Distribution strategies
Matthew Dailey (CSIM-AIT) Patterns 153 / 193
Session stateWhat is session state?
On the one hand, if you have more than a few objects without state, youprobably have a bad design.
However, statelessness for the application server is a good thing. It meansno state is retained between requests — all the state is in the database.
Query objects are the sorts of objects that can be stateless. However, youmight want to do things like keep a history of queries, in which casestatelessness would no longer do the trick for you.
If you can keep your application server stateless that’s good, because itdoesn’t matter which object serves an incoming request, and this mapswell onto HTTP (a stateless protocol).
Problem: some interactions are inherently stateful. Example: a shoppingcart in an e-commerce application. If we don’t have it we can’t makemoney!
Matthew Dailey (CSIM-AIT) Patterns 154 / 193
Session stateSession state and business transactions
Things like shopping carts are (normally) what we call SESSION STATE –state info only related to a single session. Session state is manipulatedwithin business transactions.
RECORD DATA is the long-term persistent data held in the database andvisible to all sessions. If we want to turn session state into record data, wehave to COMMIT it.
Matthew Dailey (CSIM-AIT) Patterns 155 / 193
Session stateSession state and business transactions
Since session state is part of a business transaction, it should have ACIDproperties just like any other transactional resource. However, things get abit weird.
One example is consistency: while you’re working on something like a loanapplication, the intermediate data won’t be consistent according to thebusiness logic. You often need to come up with new consistency rules forthe session state.
Isolation is another tricky thing. It means that during the businesstransaction, related record data should not change due to othertransactions.
Matthew Dailey (CSIM-AIT) Patterns 156 / 193
Session statePatterns for session state
CLIENT SESSION STATE stores the session data on the client. For aWeb presentation, it could be encoded in the URL, put inside cookies,serialized in a hidden field in a Web form, and so on. For a rich clientpresentation, it would be stored in objects and passed with each request.
SERVER SESSION STATE means the application server stores the data,either in memory, or in some persistent form on the file system or in itsown database.
DATABASE SESSION STATE means breaking the session data into tablesand storing it just like normal persistent data.
Matthew Dailey (CSIM-AIT) Patterns 157 / 193
Session stateChoosing a pattern for session state
The decision depends on many factors. First, how much data is there? Ifit’s a lot, this would rule out client session state. If it needs protection,then it’s better if it’s not stored on the client.
Moving in the other direction, isolation is tricky if you’re using databasesession data. One session should not affect another, so we need to makesure the different sessions are carefully separated in the database.
Another factor is clustering. As your application scales, you want to addapplication servers and a router to route each request to the actual serverthat will handle the request. This is more effective if we allow SESSIONMIGRATION (allowing any request to be handled by any server) ratherthan SERVER AFFINITY or STICKY SESSIONS (forcing each request ina session to be handled by the same application server). If we’re usingServer Session State, then we need to use sticky sessions, or actuallytransfer session state between application servers. This gets nasty too.
Matthew Dailey (CSIM-AIT) Patterns 158 / 193
Session stateChoosing a pattern for session state
Each pattern affects the responsiveness of the syste too. Client sessiondata needs to be transformed into objects. Server session data isimmediately available. Database session data requires a database query.
Database session state is pretty decent for retail systems and such, withinfrequent requests with little data from a large number of users. Serversession state is better for highly interactive systems like leasing systems,where a lot of data is being used on each request, and the time periodbetween requests in a session is relatively small.
In B2C and other systems we have the additional problem of users justdisappearing, and we don’t know if they’re taking a break or ending thebusiness transaction prematurely. In this case Client Session State is thewinner because we don’t have to do anything about it. Server SessionState is also OK since most frameworks have an automatic timeout. Butwith Database Session State we have to do the cleanup ourselves.
Matthew Dailey (CSIM-AIT) Patterns 159 / 193
Session stateChoosing a pattern for session state
Other issues include crashes. Database Session State is the most robust.Server Session State is sensitive to server crashes (unless the frameworkuses backing store of some kind). Client Session State is normally lostwhen the browser is closed or crashes. Surviving various crash incidentsshould be part of the non-functional requirements.
You can mix the approaches for different kinds of data. But you willalways need at least a session ID stored client side.
Fowler prefers Server Session State, with a session ID stored client side, formost applications. But you might choose Client Session State when thedata is very small or Database Client Session if you need the extrareliability and don’t worry about isolating sessions.
Matthew Dailey (CSIM-AIT) Patterns 160 / 193
Outline
1 Introduction
2 Domain logic patterns
3 Mapping to relational databases
4 Web presentation
5 Concurrency
6 Session state
7 Distribution strategies
Matthew Dailey (CSIM-AIT) Patterns 161 / 193
Distribution strategiesFirst law of distributed objects
Fowler’s First Law of Distributed Objects: don’t distribute objects! Here isan example bad design:
Fowler (2002), Fig. 7.1
Why? Because the distribution is based on domain classes, andperformance is going to be terrible.
Matthew Dailey (CSIM-AIT) Patterns 162 / 193
Distribution strategiesLocal vs. remote interfaces
Some rules of thumb:
A procedure call is fast.
A procedure call between two separate processes is orders ofmagnitude slower.
A call between separate processes on separate machines is anotherorder of magnitude slower.
Local interfaces are best when they are fine-grained. This is veryconsistent with the OO philosophy where we have many small objectsresponsible for their own small operations.
When we take an interface and make it remote, fine-grainedness is notgood. Rather than 3 calls to fetch three attributes of an object, we’dbetter get all three attributes in one call, to avoid tripling the networkoverhead.
Matthew Dailey (CSIM-AIT) Patterns 163 / 193
Distribution strategiesCoarse-grained interfaces and clustering
So any object that can be invoked over the network must have acoarse-grained interface. Any object with a fine-grained interface shouldnever be invoked over the network.
Fowler (2002), Fig. 7.2
Here is a better design based on clustering. We can do fine-grainedinterfaces between domain classes which is what we like, at the small costof having the entire application replicated at each node.
Matthew Dailey (CSIM-AIT) Patterns 164 / 193
Distribution strategiesWhen must you distribute?
Clustering can only take you so far. We do have to separate processessometimes. Here are the common reasons:
Clients and servers: it’s impractical to replicate the entire shared datastore across every PC in the enterprise. If we don’t want to usemainframes, we must have clients connecting to the data store over anetwork.
Application server/Database server: not absolutely required buttypical. For sure in an enterprise system the DB will be a separateprocess, and that gives most of the performance gap. Luckily SQL iscoarse-grained by design, to allow remote access.
Matthew Dailey (CSIM-AIT) Patterns 165 / 193
Distribution strategiesWhen must you distribute?
Other times when distribution may be necessary:
Web server/application server: try to run the application in the sameprocess as the server, but sometimes it’s not possible.
Vendor differences: any third-party application you’re using willnormally run in its own process. This will normally be acoarse-grained interface, though, so it should be OK.
Split application server software: avoid this at all costs! If it cannotbe avoided, you have to divide your software into coarse-grainedcomponents with remote interfaces.
Matthew Dailey (CSIM-AIT) Patterns 166 / 193
Distribution strategiesThe distribution boundary
Some rules of thumb for the boundary between two components with aremote interface:
At the boundaries, use coarse-grained objects that, if necessary, havea lot of attributes and operations doing a lot.
Inside each component, stick with normal fine-grained objects.
The coarse-grained boundary object should simply act as a facade forthe fine-grained objects inside.
Clients that know they’re making local calls to your component canuse the fine-grained interface directly.
The Remote Facade pattern formalizes this approach.
Data Transfer Objects go hand-in-hand with Remote Facades. Theybundle together all the data from the relevant domain objects that need tobe transferred over the remote interface.
Matthew Dailey (CSIM-AIT) Patterns 167 / 193
Distribution strategiesInterfaces for distribution
XML over HTTP is currently the most flexible method we have fortransfer of complex objects, remote procedure calls, and asynchronousmessaging over a network. SOAP is the standard.
XML has a lot of processing overhead, though. If you know that yourinterface is direct, and there are no issues with firewalls and so on, and youdon’t think you will ever want to expose an operation to otherapplications, it is best to use the direct interface, for example Java RMI.
Don’t get stuck with synchronous remote calls. Wherever possible, useasynchronous calls.
Matthew Dailey (CSIM-AIT) Patterns 168 / 193
Distribution strategiesRemote facade pattern
The Remote Facade pattern provides a coarse-grained facade onfine-grained objects to improve efficiency over a network.
Fowler (2002), p. 388
This pattern is based on GoF’s coarse-grained facade.
The facade contains no business logic – it just translates thecoarse-grained methods to underlying fine-grained objects.
Matthew Dailey (CSIM-AIT) Patterns 169 / 193
Distribution strategiesRemote facade pattern
The facade replaces individual getters and setters with bulk accessors:
Fowler (2002), Fig. 15.1
Matthew Dailey (CSIM-AIT) Patterns 170 / 193
Distribution strategiesRemote facade pattern
The facade will often be an interface to a complex web of fine-grainedobjects — on one client call, it might interact with and integrate datafrom multiple fine-grained objects.
For data transfer, if the classes are identical on both ends, and they areserializable, then we can simply serialize and transfer them.
Any remote facade can be stateful or stateless. One of the three mainsession state patterns will be needed in this stateful case.
Remote facades are good places to apply security (authentication andauthorization) and transaction control.
Remote Facade has no domain logic!! A good way to tell if you’ve violatedthis law is to see if the application can run entirely locally without theremote facade.
Matthew Dailey (CSIM-AIT) Patterns 171 / 193
Distribution strategiesRemote facade pattern example
Example: Using a Java Session Bean as a Remote Facade
EJB session beans are remote objects that can be stateful or stateless.The example has POJOs running inside an EJB container being accessedremotely through a session bean designed as a Remote Facade.
Matthew Dailey (CSIM-AIT) Patterns 172 / 193
Distribution strategiesRemote facade pattern example
Fowler (2002), Fig. 15.2
We have a decoupled Domain Model, Data Transfer Objects (package dto)to move data over the network, and remote objects (package remote) tomove data between the domain and data transfer objects. The remoteclient deals with these objects through the classes in package api.
Matthew Dailey (CSIM-AIT) Patterns 173 / 193
Distribution strategiesRemote facade pattern example
An EJB 2.1 session bean has 2 Java interfaces (AlbumService,AlbumHome) and one implementation class (AlbumServiceBean). In EJB3.0 we only need the POJO.
class AlbumService...
String play(String id) throws RemoteException;
String getAlbumXml(String id) throws RemoteException;
AlbumDTO getAlbum(String id) throws RemoteException;
void createAlbum(String id, String xml) throws RemoteException;
void createAlbum(String id, AlbumDTO dto) throws RemoteException;
void updateAlbum(String id, String xml) throws RemoteException;
void updateAlbum(String id, AlbumDTO dto) throws RemoteException;
void addArtistNamed(String id, String name) throws RemoteException;
void addArtist(String id, String xml) throws RemoteException;
void addArtist(String id, ArtistDTO dto) throws RemoteException;
ArtistDTO getArtist(String id) throws RemoteException;
Note: albums and artists are appearing in the same interface, and thereare multiple versions of the same method (accessors working on XML orDTO representations of the domain objects).
Matthew Dailey (CSIM-AIT) Patterns 174 / 193
Distribution strategiesRemote facade pattern example
The methods in the facade are simple to implement — each just delegatesto another object. Note there is no domain logic!
class AlbumServiceBean...
public AlbumDTO getAlbum(String id) throws RemoteException {
return new AlbumAssembler().writeDTO(Registry.findAlbum(id));
}
public String getAlbumXml(String id) throws RemoteException {
AlbumDTO dto = new AlbumAssembler().writeDTO(Registry.findAlbum(id));
return dto.toXmlString();
}
public void createAlbum(String id, AlbumDTO dto) throws RemoteException {
new AlbumAssembler().createAlbum(id, dto);
}
public void createAlbum(String id, String xml) throws RemoteException {
AlbumDTO dto = AlbumDTO.readXmlString(xml);
new AlbumAssembler().createAlbum(id, dto);
}
Matthew Dailey (CSIM-AIT) Patterns 175 / 193
Distribution strategiesRemote facade pattern example
public void updateAlbum(String id, AlbumDTO dto) throws RemoteException {
new AlbumAssembler().updateAlbum(id, dto);
}
public void updateAlbum(String id, String xml) throws RemoteException {
AlbumDTO dto = AlbumDTO.readXmlString(xml);
new AlbumAssembler().updateAlbum(id, dto);
}
Matthew Dailey (CSIM-AIT) Patterns 176 / 193
Distribution strategiesRemote facade pattern example
Some test code using JUnit:
This case goes from domain object to DTO to XML back to DTO.
We don’t need to deploy to the EJB container — we instantiate thesession bean directly.
class XmlTester...
private AlbumDTO kob;
private AlbumDTO newkob;
private AlbumServiceBean facade = new AlbumServiceBean();
protected void setUp() throws Exception {
facade.initializeForTesting();
kob = facade.getAlbum("kob");
Writer buffer = new StringWriter();
kob.toXmlString(buffer);
newkob = AlbumDTO.readXmlString(new StringReader(buffer.toString()));
}
public void testArtist() {
assertEquals(kob.getArtist(), newkob.getArtist());
}
Matthew Dailey (CSIM-AIT) Patterns 177 / 193
Distribution strategiesData transfer object pattern
The Data Transfer Object pattern: an object that carries data betweenprocesses in order to reduce the number of method calls.
Fowler (2002), p. 401
Each call to a remote interface is expensive, so we try to minimize thenumber of needed calls by globbing data into one class.
Matthew Dailey (CSIM-AIT) Patterns 178 / 193
Distribution strategiesData transfer object pattern
Some properties/guidelines for a Data Transfer Object (DTO):
It should hold all the data for a call.
It must be serializable (not too complex).
It should only have attributes, getters, and setters.
It often wraps more data than is really needed: sending too muchdata in one call is better than multiple calls.
The attributes need not be cohesive: a DTO often combines morethan one domain object.
Matthew Dailey (CSIM-AIT) Patterns 179 / 193
Distribution strategiesData transfer object pattern
More properties of a DTO:
Attribute types should be primitives or other DTOs (but keep thegraph simple).
The DTO classes must be present on both sides of the channel.
The DTOs might be different for different clients even when they’retransferring the same objects (e.g. XML vs. binary serialization).
You might use the same DTO for several related requests.
Some folks prefer DTOs to be immutable but it’s not necessary. Amutable DTO is good when you want to build the object up gradually.
Matthew Dailey (CSIM-AIT) Patterns 180 / 193
Distribution strategiesData transfer object pattern
Record Set is a pretty common DTO. It’s the DTO for a SQL database,and as we saw it works well with Table Modules and frameworks like .NET.
XML serialization will usually be the best choice, unless performance is aproblem. Java has built-in binary serialization, and .NET has both binaryand XML serialization.
Use an assembler to decouple the domain objects from the DTOs:
Fowler (2002), Fig. 15.4
Matthew Dailey (CSIM-AIT) Patterns 181 / 193
Distribution strategiesData transfer object pattern example
Example: transferring information about albums. Suppose we have thisdomain model:
Fowler (2002), Fig. 15.5
We’ll transfer them using this scheme:
Fowler (2002), Fig. 15.6
Matthew Dailey (CSIM-AIT) Patterns 182 / 193
Distribution strategiesData transfer object pattern example
The Assembler code:
class AlbumAssembler...
public AlbumDTO writeDTO(Album subject) {
AlbumDTO result = new AlbumDTO();
result.setTitle(subject.getTitle());
result.setArtist(subject.getArtist().getName());
writeTracks(result, subject);
return result;
}
private void writeTracks(AlbumDTO result, Album subject) {
List newTracks = new ArrayList();
Iterator it = subject.getTracks().iterator();
while (it.hasNext()) {
TrackDTO newDTO = new TrackDTO();
Track thisTrack = (Track) it.next();
newDTO.setTitle(thisTrack.getTitle());
writePerformers(newDTO, thisTrack);
newTracks.add(newDTO);
}
result.setTracks((TrackDTO[]) newTracks.toArray(new TrackDTO[0]));
}Matthew Dailey (CSIM-AIT) Patterns 183 / 193
Distribution strategiesData transfer object pattern example
private void writePerformers(TrackDTO dto, Track subject) {
List result = new ArrayList();
Iterator it = subject.getPerformers().iterator();
while (it.hasNext()) {
Artist each = (Artist) it.next();
result.add(each.getName());
}
dto.setPerformers((String[]) result.toArray(new String[0]));
}
Matthew Dailey (CSIM-AIT) Patterns 184 / 193
Distribution strategiesData transfer object pattern example
To go the other way, to update a model item from the DTO, is moredifficult. If we’re creating new ones:
class AlbumAssembler...
public void createAlbum(String id, AlbumDTO source) {
Artist artist = Registry.findArtistNamed(source.getArtist());
if (artist == null)
throw new RuntimeException("No artist named " + source.getArtist());
Album album = new Album(source.getTitle(), artist);
createTracks(source.getTracks(), album);
Registry.addAlbum(id, album);
}
private void createTracks(TrackDTO[] tracks, Album album) {
for (int i = 0; i < tracks.length; i++) {
Track newTrack = new Track(tracks[i].getTitle());
album.addTrack(newTrack);
createPerformers(newTrack, tracks[i].getPerformers());
}
}
Matthew Dailey (CSIM-AIT) Patterns 185 / 193
Distribution strategiesData transfer object pattern example
private void createPerformers(Track newTrack, String[] performerArray) {
for (int i = 0; i < performerArray.length; i++) {
Artist performer = Registry.findArtistNamed(performerArray[i]);
if (performer == null)
throw new RuntimeException("No artist named " + performerArray[i]);
newTrack.addPerformer(performer);
}
}
Note that we assume the artists are already in the Registry.
Matthew Dailey (CSIM-AIT) Patterns 186 / 193
Distribution strategiesData transfer object pattern example
To update an existing album:
class AlbumAssembler...
public void updateAlbum(String id, AlbumDTO source) {
Album current = Registry.findAlbum(id);
if (current == null)
throw new RuntimeException("Album does not exist: " + source.getTitle());
if (source.getTitle() != current.getTitle())
current.setTitle(source.getTitle());
if (source.getArtist() != current.getArtist().getName()) {
Artist artist = Registry.findArtistNamed(source.getArtist());
if (artist == null)
throw new RuntimeException("No artist named " + source.getArtist());
current.setArtist(artist);
}
updateTracks(source, current);
}
Matthew Dailey (CSIM-AIT) Patterns 187 / 193
Distribution strategiesData transfer object pattern example
private void updateTracks(AlbumDTO source, Album current) {
for (int i = 0; i < source.getTracks().length; i++) {
current.getTrack(i).setTitle(source.getTrackDTO(i).getTitle());
current.getTrack(i).clearPerformers();
createPerformers(current.getTrack(i), source.getTrackDTO(i).getPerformers());
}
}
Matthew Dailey (CSIM-AIT) Patterns 188 / 193
Distribution strategiesData transfer object pattern example
The album object is updated, not replaced, but the artists and tracks aresimply replaced. The decision here depends on whether other objects referto the objects in question.
The serialization in this case is binary. To avoid problems when the DTOobject fields are changed or reodered, we can use a map:
class TrackDTO...
public Map writeMap() {
Map result = new HashMap();
result.put("title", title);
result.put("performers", performers);
return result;
}
public static TrackDTO readMap(Map arg) {
TrackDTO result = new TrackDTO();
result.title = (String) arg.get("title");
result.performers = (String[]) arg.get("performers");
return result;
}
Matthew Dailey (CSIM-AIT) Patterns 189 / 193
Distribution strategiesData transfer object pattern example
This can be handled automatically using a reflective routine in thesuperclass:
class DataTransferObject...
public Map writeMapReflect() {
Map result = null;
try {
Field[] fields = this.getClass().getDeclaredFields();
result = new HashMap();
for (int i = 0; i < fields.length; i++)
result.put(fields[i].getName(), fields[i].get(this));
}catch (Exception e) {throw new ApplicationException (e);
}
return result;
}
Matthew Dailey (CSIM-AIT) Patterns 190 / 193
Distribution strategiesData transfer object pattern example
public static TrackDTO readMapReflect(Map arg) {
TrackDTO result = new TrackDTO();
try {
Field[] fields = result.getClass().getDeclaredFields();
for (int i = 0; i < fields.length; i++)
fields[i].set(result, arg.get(fields[i].getName()));
}catch (Exception e) {throw new ApplicationException (e);
}
return result;
}
The text has an out-of-date example of XML serialization. Nowadays folksuse XStream or other tools.
Matthew Dailey (CSIM-AIT) Patterns 191 / 193
Distribution strategiesConclusion
Remember Fowler’s first law of distribution: don’t distribute!
When you must distribute, use coarse-grained interfaces.
Matthew Dailey (CSIM-AIT) Patterns 192 / 193
Conclusion
As you begin to architect a complex system or design a component, it’scrucial to research appropriate patterns.
A novice architect with an understanding of patterns can get it right thefirst time.
An experienced architect moving into a new application area withoutexploring existing patterns may waste time or kill a project.
Avoid the NIH syndrome!
Matthew Dailey (CSIM-AIT) Patterns 193 / 193