Skip to main content

Chapter 11: Systems Scaling Up To Cross Cutting Concerns

This page is a generated reference surface for selective reading. It exists to keep the learner apps guide-first while still preserving source access.

Learning objectives

  • Explain the main ideas and vocabulary in Systems Scaling Up To Cross Cutting Concerns.
  • Work through the source examples for Systems Scaling Up To Cross Cutting Concerns without depending on raw chunk order.
  • Use Systems Scaling Up To Cross Cutting Concerns as selective reference when learner modules point back to Clean Code.

Prerequisites

  • Earlier prerequisite concepts leading into Chapter 11: Systems Scaling Up To Cross Cutting Concerns.

Module targets

  • module-05-applied-design-and-code-review

AI companion modes

  • Explain simply
  • Socratic tutor
  • Quiz me
  • Challenge my understanding
  • Diagnose my confusion
  • Generate extra practice
  • Revision mode
  • Connect forward / backward

Source-of-truth note

This unit is anchored to Clean Code and the source chapter "Chapter 11: Systems Scaling Up To Cross Cutting Concerns". Use external resources only to clarify, extend, or modernize details without replacing the chapter's conceptual spine.

External enrichment

No chapter-specific enrichment resources are curated yet. Add them in the unit manifest when a source clearly improves learning.

Source provenance

  • Primary source: Clean Code
  • Source chapter 11: Chapter 11: Systems Scaling Up To Cross Cutting Concerns
  • Raw source file: 045-chapter-11-systems-scaling-up-to-cross-cutting-concerns.md

Merged source

Chapter 11 Systems Scaling Up To Cross Cutting Concerns

Chapter 11: Systems: Scaling Up to Cross-Cutting Concerns

Scaling Up

Cities grow from towns, which grow from settlements. At first the roads are narrow and practically nonexistent, then they are paved, then widened over time. Small buildings and

  1. See, for example, [Fowler]. 4. See [Spring]. There is also a Spring.NET framework. 5. Don't forget that lazy instantiation/evaluation is just an optimization and perhaps premature!

empty plots are filled with larger buildings, some of which will eventually be replaced with skyscrapers. At first there are no services like power, water, sewage, and the Internet (gasp!). These services are also added as the population and building densities increase. This growth is not without pain. How many times have you driven, bumper to bumper through a road "improvement" project and asked yourself, "Why didn't they build it wide enough the first time!?" But it couldn't have happened any other way. Who can justify the expense of a sixlane highway through the middle of a small town that anticipates growth? Who would want such a road through their town? It is a myth that we can get systems "right the first time." Instead, we should implement only today's stories, then refactor and expand the system to implement new stories tomorrow. This is the essence of iterative and incremental agility. Test-driven development, refactoring, and the clean code they produce make this work at the code level. But what about at the system level? Doesn't the system architecture require preplanning? Certainly, it can't grow incrementally from simple to complex, can it?

Software systems are unique compared to physical systems. Their architectures can grow incrementally, if we maintain the proper separation of concerns.

The ephemeral nature of software systems makes this possible, as we will see. Let us first consider a counterexample of an architecture that doesn't separate concerns adequately. The original EJB1 and EJB2 architectures did not separate concerns appropriately and thereby imposed unnecessary barriers to organic growth. Consider an Entity Bean for a persistent Bank class. An entity bean is an in-memory representation of relational data, in other words, a table row. First, you had to define a local (in process) or remote (separate JVM) interface, which clients would use. Listing 11-1 shows a possible local interface:

Listing 11-1

An EJB2 local interface for a Bank EJB
package com.example.banking;
import java.util.Collections;
import javax.ejb.*;
public interface BankLocal extends java.ejb.EJBLocalObject {
String getStreetAddr1() throws EJBException;
String getStreetAddr2() throws EJBException;
String getCity() throws EJBException;
String getState() throws EJBException;
String getZipCode() throws EJBException;
void setStreetAddr1(String street1) throws EJBException;
void setStreetAddr2(String street2) throws EJBException;
void setCity(String city) throws EJBException;
void setState(String state) throws EJBException;

Listing 11-1 (continued)

An EJB2 local interface for a Bank EJB
void setZipCode(String zip) throws EJBException;
Collection getAccounts() throws EJBException;
void setAccounts(Collection accounts) throws EJBException;
void addAccount(AccountDTO accountDTO) throws EJBException;
}

I have shown several attributes for the Bank's address and a collection of accounts that the bank owns, each of which would have its data handled by a separate Account EJB.

Listing 11-2 shows the corresponding implementation class for the Bank bean.

Listing 11-2

The corresponding EJB2 Entity Bean Implementation
package com.example.banking;
import java.util.Collections;
import javax.ejb.*;
public abstract class Bank implements javax.ejb.EntityBean {
// Business logic...
public abstract String getStreetAddr1();
public abstract String getStreetAddr2();
public abstract String getCity();
public abstract String getState();
public abstract String getZipCode();
public abstract void setStreetAddr1(String street1);
public abstract void setStreetAddr2(String street2);
public abstract void setCity(String city);
public abstract void setState(String state);
public abstract void setZipCode(String zip);
public abstract Collection getAccounts();
public abstract void setAccounts(Collection accounts);
public void addAccount(AccountDTO accountDTO) {
InitialContext context = new InitialContext();
AccountHomeLocal accountHome = context.lookup("AccountHomeLocal");
AccountLocal account = accountHome.create(accountDTO);
Collection accounts = getAccounts();
accounts.add(account);
}
// EJB container logic
public abstract void setId(Integer id);
public abstract Integer getId();
public Integer ejbCreate(Integer id) { ... }
public void ejbPostCreate(Integer id) { ... }
// The rest had to be implemented but were usually empty:
public void setEntityContext(EntityContext ctx) {}
public void unsetEntityContext() {}
public void ejbActivate() {}
public void ejbPassivate() {}
public void ejbLoad() {}
public void ejbStore() {}
public void ejbRemove() {}
}

I haven't shown the corresponding LocalHome interface, essentially a factory used to create objects, nor any of the possible Bank finder (query) methods you might add. Finally, you had to write one or more XML deployment descriptors that specify the object-relational mapping details to a persistence store, the desired transactional behavior, security constraints, and so on. The business logic is tightly coupled to the EJB2 application "container." You must subclass container types and you must provide many lifecycle methods that are required by the container. Because of this coupling to the heavyweight container, isolated unit testing is difficult. It is necessary to mock out the container, which is hard, or waste a lot of time deploying EJBs and tests to a real server. Reuse outside of the EJB2 architecture is effectively impossible, due to the tight coupling. Finally, even object-oriented programming is undermined. One bean cannot inherit from another bean. Notice the logic for adding a new account. It is common in EJB2 beans to define "data transfer objects" (DTOs) that are essentially "structs" with no behavior. This usually leads to redundant types holding essentially the same data, and it requires boilerplate code to copy data from one object to another.

Cross-Cutting Concerns

The EJB2 architecture comes close to true separation of concerns in some areas. For example, the desired transactional, security, and some of the persistence behaviors are declared in the deployment descriptors, independently of the source code. Note that concerns like persistence tend to cut across the natural object boundaries of a domain. You want to persist all your objects using generally the same strategy, for example, using a particular DBMS6 versus flat files, following certain naming conventions for tables and columns, using consistent transactional semantics, and so on. In principle, you can reason about your persistence strategy in a modular, encapsulated way.Yet, in practice, you have to spread essentially the same code that implements the persistence strategy across many objects. We use the term cross-cutting concerns for concerns like these. Again, the persistence framework might be modular and our domain logic, in isolation, might be modular. The problem is the fine-grained intersection of these domains. In fact, the way the EJB architecture handled persistence, security, and transactions, "anticipated" aspect-oriented programming (AOP),7 which is a general-purpose approach to restoring modularity for cross-cutting concerns. In AOP, modular constructs called aspects specify which points in the system should have their behavior modified in some consistent way to support a particular concern. This specification is done using a succinct declarative or programmatic mechanism.

  1. Database management system. 7. See [AOSD] for general information on aspects and [AspectJ]] and [Colyer] for AspectJ-specific information.

Using persistence as an example, you would declare which objects and attributes (or patterns thereof) should be persisted and then delegate the persistence tasks to your persistence framework. The behavior modifications are made noninvasively8 to the target code by the AOP framework. Let us look at three aspects or aspect-like mechanisms in Java.