Of the refactoring processes described in this book, extracting domain logic is going to be the most difficult, time consuming, and detail-oriented. This is a very tough thing to do, and it requires a lot of care and attention. The domain logic is the very core of our legacy application, and we need to make sure to pull out just the right parts. This means success is completely dependent on our familiarity and competence with the the legacy application as it exists now.

Luckily, our prior exercises in modernizing our legacy codebase have given us a broad overview of the application as a whole, as well as deep knowledge of the specific parts we have had to extract and refactor. This should endow us with the confidence to complete this task successfully. It is a demanding, but ultimately satisfying, activity.

In general, we proceed as follows:

As in earlier chapters, we use our project-wide search facility to find where we create new instances of Gateway classes:

Search for:

The new Gateway instance may be used directly in a page script, in which case we have found some candidate code for extracting domain logic. If the Gateway instance is injected into a class, we now need to dive into that class to find where the Gateway is used. The code surrounding that usage will be our candidate for extracting domain logic.

After we have found some candidate code using a Gateway, we need to examine the code surrounding Gateway usage for these and other operations:

These and other pieces of logic are likely to be domain-related.

To successfully extract the domain logic to one or more Transactions classes and methods, we will have to perform these and other activities:

By way of example, recall the code we started with in Appendix B, Code before Gateways. Earlier in this chapter we mentioned that we had extracted embedded SQL statements to ArticlesGateway classes, ending up with the code in Appendix C, Code after Gateways. We now go from that to Appendix D, Code after Transaction Scripts where we have extracted the domain logic to an ArticleTransactions class.

The extracted domain logic does not appear particularly complicated in its completed form, but actually doing the work turns out to be quite detailed. Review the Appendix C, Code after Gateways and compare to the Appendix D, Code after Transaction Scripts. Among other things, we should find the following:

After the extraction, we have a classes/ directory structure that looks something like the following. This is a result of using a domain-oriented class structure when we extracted SQL to Gateway classes:

In the examples, we showed Transactions as a collection of methods related to a particular domain entity, such as ArticleTransactions. Each part of the domain logic related to that entity was wrapped in a class method.

However, it is also reasonable to break up domain logic into a one-class-per-transaction structure. Indeed, some transactions may be complex enough that they truly require their own separate classes. There is nothing wrong with using a single class to represent a single domain logic transaction.

For example, the earlier ArticleTransactions class might be split into an abstract base class with support methods, and two concrete classes for each of the extracted pieces of domain logic. Each of the concrete classes extends the AbstractArticleTransaction, like so:

If we use a one-class-per-transaction approach, what do we name the main method on the single-transaction class, the one that actually performs the transaction? If there is a common convention for main methods that already exist in our legacy codebase, we should adhere to that convention. Otherwise, we need to pick a single consistent method name. Personally, I enjoy co-opting the __invoke() magic method for this purpose, but you may wish to use exec() or some other appropriate term to indicate we are executing or otherwise performing the transaction.