I dug out a really old book over the last weekend, from 1993, 1Peter Coad and Jill Nicola’s “Object-Oriented Programming”.

This 555 page (+ index) book (and it included a 3.5inch disk), has the blurb on the back:

Real Programming… not just language syntax.

And…

Experience OOP first-hand:

  • Learn how to “object think”…via engaging, real-world examples
  • Learn how to program in C++ and Smalltalk…with parallel examples.
  • Apply reuse strategies…for current reuse and future reuse.
  • Apply advanced features…C++ templates…and Smalltalk model-view-controller

It’s a book about Object-Oriented Analysis and Design (OOAD), with examples in Smalltalk and C++.

OOAD was a process developed by Coad and Yourdon, and differentiated itself from “Structured Analysis and Design”, which is what I learned way back 30 years ago.

But, it’s more than that, it has handy language summaries, where they managed to condense Smalltalk into just 31 pages, and remarkably, C++ into just 10!

I think I first bought this book in my original Smalltalk-phase, somewhere around 1998, when I was trying to understand how to design Smalltalk systems, it’s fairly unusual, the chapters alternate between C++ and Smalltalk implementations of the “real-world examples”.

There are a number of interesting principles in the book, written early on in the “object-oriented era”, some of these are still relevant, and worth digging into.

The “heart and soul” principle”

From page 230 of the book:

Make an extensive list of classes in a problem domain. Then select a small number of classes…ones that together represent the heart and soul, the core of the problem domain under consideration. Do OOA and OOD on these first. Build them. Demonstrate working results. Then go for more.

This is really about identifying what makes an application important to the business, this is also the hallmark of the “Hexagonal Architecture”, the central hexagon is the business logic, and this will normally be the “heart and soul” of the system.

I’ve run a number of “code-reading” sessions, getting engineers to read and understand codebases they’re unfamiliar with, appreciate what makes code easy and hard to understand, and identify patterns in the code, I’ll write more about this at some point, but one of the things I discuss, is identifying the core parts of the a service, as a way to understand what it does.

This is like reverse-engineeering the “heart and soul” of the problem domain, starting with the premise that the code has a clear domain purpose, and working out what it is.

An example - UK Government Pay Ledger

The trick to learning from reading code, is to pick unfamiliar code-bases, I’ll start with https://github.com/alphagov/pay-ledger.

The description is “Provide aggregate reporting on payment events”, and the README has “This is a bare bones Dropwizard App for the Pay Ledger microservice.”

So, probably makes sense to the business…

It’s using Dropwizard, and I’m fairly familiar with how a Dropwizard application is structured, but it’s just a Java application, so there should be a “main” entry point.

$ find src -name "*.java" | xargs grep main
src/main/java/uk/gov/pay/ledger/app/LedgerApp.java:    public static void main(String[] args) throws Exception {

Looking in the LedgerApp class, there are the following methods:

public class LedgerApp extends Application<LedgerConfig> {
    public static void main(String[] args) throws Exception {}

    @Override
    public void initialize(Bootstrap<LedgerConfig> bootstrap) {}

    @Override
    public void run(LedgerConfig config, Environment environment) {}
}

main just executes LedgerApp, initialize sets up some bundles, and the run method is starting the main Dropwizard application, by registering some Jersey resources, which are REST endpoints.

EventResource

The first registered resource the EventResource:

environment.jersey().register(injector.getInstance(EventResource.class));

Looking at the EventResource class:

@Path("/v1/event")
@Produces(APPLICATION_JSON)
public class EventResource {
    private final EventDao eventDao;

    @Inject
    public EventResource(EventDao eventDao) {
        this.eventDao = eventDao;
    }

    @Path("/{eventId}")
    @GET
    public Event getEvent(@PathParam("eventId") Long eventId) {
        return eventDao.getById(eventId)
                .orElseThrow(() -> new WebApplicationException(Response.Status.NOT_FOUND));
    }
}

It exposes an endpoint GET /v1/event/{eventId} and looking at the code, it uses an EventDao object to find the event by the URL provided ID, and returns it, or NOT_FOUND.

So, I have a core entity Event.

TransactionResource

The second registered resource is the TransactionResource:

environment.jersey().register(injector.getInstance(TransactionResource.class));

Again, looking at the TransactionResource:

@Path("/v1/transaction")
@Produces(APPLICATION_JSON)
public class TransactionResource {
    private final TransactionService transactionService;

    @Inject
    public TransactionResource(TransactionService transactionService) {
        this.transactionService = transactionService;
    }

    @Path("/{transactionExternalId}")
    @GET
    public TransactionView getById(@PathParam("transactionExternalId") String transactionExternalId,
                                   @QueryParam("account_id") String gatewayAccountId,
                                   @QueryParam("override_account_id_restriction") Boolean overrideAccountRestriction,
                                   @QueryParam("transaction_type") TransactionType transactionType,
                                   @QueryParam("parent_external_id") String parentTransactionExternalId) {}

    @Path("/")
    @GET
    public TransactionSearchResponse search(@Valid
                                            @BeanParam TransactionSearchParams searchParams,
                                            @QueryParam("override_account_id_restriction") Boolean overrideAccountRestriction,
                                            @QueryParam("account_id") String gatewayAccountId,
                                            @Context UriInfo uriInfo) {}

    @Path("{transactionExternalId}/event")
    @GET
    public TransactionEventResponse events(@PathParam("transactionExternalId") String transactionExternalId,
                                           @QueryParam("gateway_account_id") @NotEmpty String gatewayAccountId,
                                           @QueryParam("include_all_events") boolean includeAllEvents,
                                           @Context UriInfo uriInfo) {}

    @Path("/{parentTransactionExternalId}/transaction")
    @GET
    public TransactionsForTransactionResponse getTransactionsForParentTransaction(@PathParam("parentTransactionExternalId") String parentTransactionExternalId,
                                                                                  @QueryParam("gateway_account_id") @NotEmpty String gatewayAccountId
    ) {}
}

This is a significantly bigger resource, exposing four different endpoints:

  • GET /v1/transaction/{transactionExternalId}
  • GET /v1/transaction/
  • GET /v1/transaction/{transactionExternalId}/event
  • GET /v1/transaction/{parentTransactionExternalId}/transaction

And it delegates to a TransactionService to fetch these, there’s a lot of flexibility in the querying of transactions, which means I have my second core entity Transaction.

These make sense for a service with a description of “Provide aggregate reporting on payment event”, clearly the EventResource exposes these payment events and the TransactionResource provides the reporting, which explains the flexibility in the querying API.

There are two types of classes returned by the endpoints TransactionView and 3 Response classes, TransactionSearchResponse, TransactionEventResponse and TransactionsForTransactionResponse.

Back to main

Continuing with the main setup:

environment.jersey().register(injector.getInstance(HealthCheckResource.class));
environment.healthChecks().register("sqsQueue", injector.getInstance(SQSHealthCheck.class));

if(config.getQueueMessageReceiverConfig().isBackgroundProcessingEnabled()) {
    environment.lifecycle().manage(injector.getInstance(QueueMessageReceiver.class));
}

From this, there’s a Healthchecks endpoint, which exposes the state of the healthchecks, and specific sqsQueue check, this is a giveaway that this service is receiving or publishing data to AWS SQS.

Finally, there is a check to see if “background processing is enabled”, and if so, it starts a QueueMessageReceiver, with the sqsQueue above, it I can infer that it’s receiving data via SQS.

Class summary so far

A quick check indicates there are 61 classes in this application, and I’ve identified about a quarter of them:

  • EventResource, Event and EventDao
  • TransactionResource, Transaction, TransactionService, TransactionView, TransactionSearchResponse, TransactionEventResponse and TransactionsForTransactionResponse
  • HealthCheckResource
  • SQSHealthCheck
  • QueueMessageReceiver

At this point, the mental model I have involves the Event, Transaction and the QueueMessageReceiver.

In part two, I’ll sketch out the relationship between these.

  1. Peter Coad and Edward Yourdon. 1991. Object-Oriented Design. Yourdon Press, Upper Saddle River, NJ, USA.