Design Pattern Evangelist Blog

Smart pointers about software design

Hexagonal Architecture – Adapter Flexibility

How a few design options in the Adapter Layer provides more flexibility


Introduction

This continues the Hexagonal Architecture series with how Hexagonal Architecture affords itself to flexibility, mainly in the Adapter Layer.

In the Why It Works blog, I featured knowledge/dependency theory, and why I feel that the practice of that theory is one of the main reasons that Hexagonal Architecture works so well. This blog entry will expand upon those ideas with some additional design options that may be useful in designs that feature Hexagonal Architecture principles.

These additional design options are not my original techniques but applying them within the context of Hexagonal Architecture is my idea. I’m branching away from Alistair Cockburn and Bob Martin, who created and popularized this architecture/design. Their presentations tend to be more bounded and focused upon the architecture whereas I am more interested in the design. Architecture is a subset of design, but I think that good design should be applied to any layer of abstraction.

Alistair Cockburn posted this on X/Twitter in September 2023:

For those who keep asking about #hexagonalarchitecture layers, here it is: There are only 2 layers: inside. outside. No relation to layers in Clean, Onion, DDD, Modular Monolith, nothing. Stop dragging them into the pic. What you do inside each layer is all your biz, not mine.

I interpret his statement as saying that Hexagonal Architecture (Ports and Adapters) is only about the Port and the Adapter. The Port is inside. The Adapter is outside.

But I think he’s discarding so much richness of the design with this constrained definition. He views this design as restricted to only those elements that I represent on either side of the Red Hexagon in my design. I include more elements to the basic design, such as the Purple Hexagon and the Adapters relationships with External Frameworks and External Dependencies.

So much of Hexagonal design applies to Clean, Onion, DDD, Modular Monolith and more. I plan to blog (TBD) about the relationship of Hexagonal Architecture to these concepts. I’ve already blogged about the relationship of Hexagonal Architecture to Clean Architecture in Hexagonal/Clean Compare and Contrast.

The Clean Architecture

Bob Martin adds a few more layers to Clean Architecture than Cockburn does in Hexagonal Architecture. Martin depicts dependency from the outer most Frameworks and Drivers ring pointing inward toward the Interface Adapters ring. I think this is the wrong direction as I mentioned in the Frameworks and Drivers section of the comparison blog. I represent this boundary as a Purple Hexagon, and in my diagrams the knowledge and dependency arrows point outward. My outward arrows will become important later.

Refresher

This blog is a continuation of Why It Works. I’ll highlight a few points here from that blog starting with my basic Hexagonal Architecture diagram:

Hexagonal Architecture

Pure Stable/Fixed elements have arrows pointing into them, meaning they depend upon nothing. These elements in Hexagonal Architecture include:

Pure Unstable/Flexible elements have arrows pointing away from them, meaning nothing in the design depends upon them. They are invisible to the other elements in the design. These elements in Hexagonal Architecture include:

Technically the Configurer and the Purple Hexagon Boundary are the only Pure Unstable/Flexible elements. Business Logic and Adapters are pseudo-pure. They only have a creation arrow pointing into them, which originate from the pure Configurer. I consider that pure enough for my needs.

Almost all design options presented here will be with these Pure Unstable/Flexible elements. All the fun is in the Adapter zone. Red Hexagons are mostly unchanged in the diagrams. They don’t change as we flex the Adapters. Likewise, the Red External Frameworks and Dependencies don’t change either.

I is for Interface, or is it?

Interface names start with I in C# by convention. It’s reminiscent of Hungarian Notation, which I’ve not much cared for myself. Hungarian Notation always reminded me of Klingon.

Here’s what Bob Martin has to say about this practice in his book: Clean Code:

These are sometimes a special case for encodings. For example, say you are building an ABSTRACT FACTORY for the creation of shapes. This factory will be an interface and will be implemented by a concrete class. What should you name them? IShapeFactory and ShapeFactory? I prefer to leave interfaces unadorned. The preceding I, so common in today’s legacy wads, is a distraction at best and too much information at worst. I don’t want my users knowing that I’m handing them an interface. I just want them to know that it’s a ShapeFactory. So if I must encode either the interface or the implementation, I choose the implementation. Calling it ShapeFactoryImp, or even the hideous CShapeFactory, is preferable to encoding the interface.

I tend to agree with him on this. I avoided the I prefix for interfaces in my career. Then a few months ago, I stumbled upon this blog: I, Interface. The author suggested thinking of I as First Person Singular: I rather than Interface. This leads to interface names that still have the I prefix, but in a more declarative way that provides more context. For example, we can define interfaces with names like:

I’ll be using this convention through most of my examples. Alistair Cockburn does something similar but with a slightly different structure. His naming convention starts with For as in:

Both declare context, but I rather like the I prefix convention, and it’s consistent with other I interface conventions.

Deep and wide. Deep and wide. There’s a fountain flowing deep and wide

Previous Hexagonal Architecture diagrams I’ve presented have mostly only included one Framework Adapter and one Dependency Adapter mainly to present the concepts as simply as possible. The Hexagonal Architecture design is not restricted to one of each.

We have design flexibility in behavior and structure and then within each we have design flexibility with breadth and depth.

Behavior and Structure

I borrowed these terms from Design Patterns, which organizes most patterns into two categories:

I’ll be using them to distinguish between flexibility that’s used because the behavior defined in the domain requires it versus flexibility that’s not behavior driven, but it’s useful to keep the design more modular and flexible.

It’s somewhat inspired by a quote attributed to Alistair Cockburn:

If it’s your decision, it’s design; if not, it’s a requirement.

Behavior variations are those that we have to do. Structural variations are those that we choose to do.

Behavior and Breadth

The behavior can expand through breadth as needed. The Hexagonal Architecture can support multiple frameworks and dependencies. It does so, because the behavior requires it, meaning that it’s a requirement.

Consider this diagram where the Business Logic needs to persist stuff, publish events and send email. The Business Logic implementation will have references to these interfaces because the core domain behavior requires it.

Hexagonal Architecture with Multiple Adapters

The Business Logic defines three dependencies based upon its domain:

Even without any code, this design hopefully makes sense.

There are two frameworks:

ManageStuff has no dependency or knowledge upon either of these Frameworks or the External Dependencies. ManageStuff is loosely coupled to the Frameworks and Dependencies through Adapters, which are created via the Configurer. None of the classes depend upon nor have knowledge of each other.

I didn’t have room to include the Configurer in the diagram, but if I had drawn it, it would have only been a purple rectangle with creation lines to each of the blue rectangles. Here’s what the Configurer class might look like in Java:

IManageStuff manageStuff =
    new ManageStuff(
        new PersisStuffViaDB(),
        new PublishStuffViaKafka(),
        new SendEmailViaSpartPost()
    );

ManageStuffFromAndroid manageStuffFromAndroid = new ManageStuffFromAndroid(manageStuff);
ManageStuffFromRest manageStuffFromRest = new ManageStuffFromRest(manageStuff);

This probably isn’t exactly the way the Configurer would work. Most likely, there would be two Configurers, one for each Framework option. The REST based configuration would be similar to the above, except that manageStuffFromAndroid would not be created, and its name would probably be RestConfigurerToManageStuff. The other Configurer would be for the Android Framework, and its name would probably be AndroidConfigurerToManageStuff. AndroidConfigurerToManageStuff would create the Adapters as well, but these would most likely be different Adapters that implement the dependency interfaces and delegate to a different set of External Dependencies that are part of the Android environment.

Multiple Frameworks and External Dependencies is why Alistair Cockburn chose a hexagon in his initial diagrams. When he presented the design with hand drawn images, each framework/dependency port/adapter pair had its own facet, which he represented as a side of the hexagon. He wanted to convey that notion of pluggable ports around a polygon, and he wanted more sides, or facets, than the more traditional rectangle. Here’s an example:

Hexagonal Architecture with Facets

Behavior and Depth

Many Hexagonal Architectures represent, or give the impression, that external frameworks and dependencies must be resources outside of the system, such as external vendor products, open source, etc. I view external frameworks and dependencies as anything that resides outside of the Red Hexagon regardless of its source. It could easily be another service in the system. Consider this diagram where the primary business logic to Place Orders loosely interacts with an Inventory Service, which is still part of the product but still outside of the Red Hexagon.

In my view, it should not matter to the Red Hexagon whether its Adapters are connecting it to an external vendor, open source or another service or component of the system.

Hexagonal Architecture delegating to another service

This diagram features two Red Hexagons for two sets of interface contracts:

The Red Hexagons do not know about each other. The glue that connects them is HandlePlacedOrdersViaManageInventory, but it’s really not the glue. It’s the Configurer that glues all of the pieces together. Here’s how it might look in Java:

PlaceOrdersFromREST placeOrdersFromREST =
    new PlaceOrdersFromREST(
        new PlaceOrders(
            new HandlePlacedOrdersViaManageInventory(
                new ManageInventory(
                    new PersistInventoryViaDB()
                )
            )
        )
    );

Let’s remove the Hexagons and the Configurer to see what’s left.

Hexagonal Architecture delegation chain

Polymer chain

This chain can go on indefinitely. This repeating sequence pattern reminds me of a descending staircase. The repeating pattern alternates Strategy and Adapter patterns. It can continue indefinitely, much like polymer chains.

Structure and Breadth

Imagine you’re working with this straightforward design:

Basic Hexagonal Architecture

Your architect declares that the system is going to be more distributed, and that Domain Events will be added. So, when Stuff is persisted, then a Domain Event, such as PersistedStuff, needs to be created and disseminated on a Message Service.

The diagram in the Breadth and Behavior section above shows how this could be done with a new contract for Publishing Events, but is this really a Behavior update? Is the Customer or even the Product Manager asking for Domain Events? Probably not. Consider Cockburn’s quote above, this is not a requirement. It’s a design decision. We’d like to be able to add Domain Events without touching anything inside the Red Hexagon.

The first consideration may be to update, or really replace, PersistStuffViaDB with a new Adapter that persists and sends event notifications, possibly with the name: PersistStuffViaDBAndNotifyStuffViaMessageService. This is one of the reasons that we favor Adapters. They allow us to swap one out for another one smoothly.

The name is rather long, but that’s not my main issue with it. It contains And. This method is doing more than one thing. We may break the DB functionality when adding the Message Service functionality. The Adapter has more than one reason to be changed. In the future it could be updated because of DB dependencies or Message Service dependencies. An update to one feature runs the risk of breaking the other feature. This And Adapter violates the Single Responsibility Principle (SRP).

Consider this design enhancement, which adds a new Adapter for the Message Service.

Basic Hexagonal Architecture with Message Service

We no longer have an SRP violation. This is an application of the Strategy design pattern.

But there’s still a problem. ManageStuff only has one reference to IHandleStuff, but now we have two classes that implement IHandleStuff. We could update ManageStuff to have two references to IHandleStuff, but now we’re making changes in the Red Hexagon, and we want to avoid that, since this is an architecture update and not a behavior update.

One solution to this problem is to employ the Composite design pattern. Here is the design diagram followed by some implementation snippets to help describe it. Notice that the only difference between this diagram and the one above is the new HandleStuffViaComposite class and an update to the Configurer. Not only is the Red Hexagon content unaffected, but the other Adapters are unaffected as well.

Basic Hexagonal Architecture with Composite

Here’s a brief summary of how Composite can solve our problem.

Let’s assume that this is the declaration for IHandleStuff:

interface IHandleStuff {
    void handle(Stuff stuff);
}

NOTE: The IHandleStuff interface really has no context. And the notion of possible Exceptions is ignored. I’m more focused upon how this will handle sunny day scenarios. A real interface and subsequent implementations would require more context and rainy-day scenarios as well.

Each of the three Adapters has to implement this interface. PersistStuffViaDB will handle stuff in the DB. NotifyStuffViaMessageService will create a StuffHandled Domain Event and send notifications via the Message Service.

But things get very interesting with HandleStuffViaComposite. It doesn’t have any external dependencies. Its only dependency is upon IHandleStuff both as its parent interface as well as referenced interfaces. The diamond indicates it has a Container of IHandleStuff references. Here’s the gist of what HandleStuffViaComposite would look like in Java:

class HandleStuffViaComposite implements IHandleStuff {
    private List<IHandleStuff> stuffIHandle = new LinkedList<>();

    public void add(IHandleStuff iHandleStuff) {
        stuffIHandle.add(iHandleStuff);
    }

    public void handle(Stuff stuff) {
        for (IHandleStuff iHandleStuff : stuffIHandle) {
            iHandleStuff.handle(stuff);
        }
    }
}

So far, this is only potential. We need the Configurer to put all the pieces together. The Configurer would look something like this:

HandleStuffViaComposite handleStuffViaComposite = new HandleStuffViaComposite();
handleStuffViaComposite.add(new PersistStuffViaDB());
handleStuffViaComposite.add(new NotifyStuffViaMessageService());

ManageStuffFromRest manageStuffFromRest =
    new ManageStuffFromRest(
        new ManageStuff(handleStuffViaComposite)
    );

Here the sequence of what happens when ManageStuff makes a call to: iHandleStuff.handle(stuff):

  1. handleStuffViaComposite, which is the reference for iHandleStuff in ManageStuff, will iterate through its List of iHandleStuff references with the first being persistStuffViaDB and the second one being notifyStuffViaMessageService.
  2. handleStuffViaComposite will invoke persistStuffViaDB.handle(stuff), which will persist stuff in the DB.
  3. handleStuffViaComposite will invoke notifyStuffViaMessageService.handle(stuff), which will create the Domain Event and send notifications via the Message Service.
  4. It returns to ManageStuff.

HandleStuffViaComposite is a List of IHandleStuff references. It can be configured to manage as many IHandleStuff Adapters as needed.

Structure and Depth

Composite handles breadth, but what about depth? Guess what? Composite handles it as well. HandleStuffViaComposite is a List of IHandleStuff references, and it, itself, is also an IHandleStuff reference. It is self-referential, and it can contain references to other HandleStuffViaComposite references.

WARNING: A HandleStuffViaComposite reference should never contain a reference to itself either directly or indirectly. If it were to contain a reference to itself, then its call to handle(stuff) would get stuck in an infinite loop.

Let’s further assume that your architect has decided to send Domain Events to two Message Services, Kafka and RabbitMQ. We can remove NotifyStuffViaMessageService and replace it with two more Adapter classes:

Here’s the updated design with more concrete Message Service Adapters:

Basic Hexagonal Architecture with Composite

It’s practically identical to the previous design. The only difference is in the Message Service Adapters and that we’re going to configure a HandleStuffViaComposite to contain another HandleStuffViaComposite. Then we can update the Configurer as follows:

HandleStuffViaComposite notifyStuffViaMessageServices = new HandleStuffViaComposite();
notifyStuffViaMessageServices.add(new NotifyStuffViaKafka());
notifyStuffViaMessageServices.add(new NotifyStuffViaRabbitMQ());

HandleStuffViaComposite handleStuffViaComposite = new HandleStuffViaComposite();
handleStuffViaComposite.add(new PersistStuffViaDB());
handleStuffViaComposite.add(notifyStuffViaMessageServices);

ManageStuffFromRest manageStuffFromRest =
    new ManageStuffFromRest(
        new ManageStuff(handleStuffViaComposite)
    );

And that’s it. This Composite is a List, where one of the elements in the List also contains a List. We can think of Composite as a Tree where each non-terminal node that is a Composite object can branch to as many child nodes as possible, including more Composite object nodes.

Every time we add a terminal concrete node to a Composite, we’re expanding in breadth. Every time we add a non-terminal Composite node, we’re expanding in depth. Composite is one of my favorite design patterns. So little code and yet so many options.

Inspired By True Events

I’ve mostly been doing a lot of verbal handwaving so far. Let’s get into a real scenario. This story is inspired by true events, but I’m going to reduce it to its core elements.

I worked on a project where one of the features stored Documents for people. These Documents could be Word documents, PDFs, etc. For years, they were stored in a traditional database as a blob of bytes, because that was the only storage mechanism we had. The simplified diagram would look like this:

Basic Hexagonal Architecture with Document with DB

This worked fine for a while, but we wanted to move from a monolith architecture to a more distributed architecture, and since Documents are just blobs of bytes, we wanted to store them in Cloud Storage.

We couldn’t do a flash cut, because the product already had hundreds of millions of Documents in the traditional DB, and it would take too long to migrate them to Cloud Storage during a maintenance window, which we want to keep as short as possible. We could migrate the Documents via batch processing. This would take days if not weeks, so how do we ensure that the correct Document is always managed properly during the migration period?

We embarked on something called Dual Reads and Writes. Basically, the feature would delegate to two sets of datastores, DB and Cloud Storage, during the migration period. Documents would be managed in both. This is a structural choice, not a behavior choice. So, we don’t to avoid updates to the Red Hexagon as much as possible.

I’m going to simplify this and sanitize it quite a bit. The product contained 20 years of legacy code. It wasn’t as clean as the diagram above suggests. I’ll omit the trials and tribulations until we converged upon a design much like this.

Basic Hexagonal Architecture with Document with Dispatcher

PersistDocumentsViaDispatching was the key to make it all work. This class is a variation of Composite. It’s self-referential, but it’s not going to be quite as flexible as the previous Composite example. PersistDocumentsViaDispatching will have indirect knowledge of the DB and Cloud Storage Adapters. Dispatching rules are also FeatureFlag controlled.

Let’s look at some implementations.

I’m going to omit update operation to keep things simpler:

interface IPersistDocuments {

    void add(PersonId personId, Document document);

    Optional<Document> getByPersonId(PersonId personId);

    void delete(PersonId personId);
}

PersistDocumentsViaDB would implement these methods and delegate to the DB. PersistDocumentsViaCloudStorage would implement them and delegate to Cloud Storage.

PersistDocumentsViaDispatching is more interesting, and keep in mind that more was needed for the real implementation, but this is the gist of it:

class PersistDocumentsViaDispatching implements IPersistDocuments {
    private final FeatureFlags featureFlags;
    private final IPersistDocuments iPersistDocumentsViaDB;
    private final IPersistDocuments iPersistDocumentsViaCloudStorage;

    public PersistDocumentsViaDispatching(FeatureFlags featureFlags, IPersistDocuments iPersistDocumentsViaDB, IPersistDocuments iPersistDocumentsViaCloudStorage) {
        this.featureFlags = featureFlags;
        this.iPersistDocumentsViaDB = iPersistDocumentsViaDB;
        this.iPersistDocumentsViaCloudStorage = iPersistDocumentsViaCloudStorage;
    }

    public void add(PersonId personId, Document document) {
        if (featureFlags.isEnabled(CLOUD_SERVICE)) {
            iPersistDocumentsViaCloudStorage.add(personId, document);
        }

        if (featureFlags.isEnabled(DB)) {
            iPersistDocumentsViaDB.add(personId, document);
        }
    }

    public Optional<Document> getByPersonId(PersonId personId) {
        Optional<Document> documentViaCloudStorage = iPersistDocumentsViaCloudStorage.getByPersonId(personId);

        if (documentViaCloudStorage.isPresent()) {
            return documentViaCloudStorage;
        }

        Optional<Document> documentViaDB = iPersistDocumentsViaDB.getByPersonId(personId);
        if (documentViaDB.isPresent()) {
            if (!featureFlags.isEnabled(DB)) {
                internalNotification("Document not found in Cloud Storage, but found in DB when DB is disabled for PersonId = {}", personId);
            }
            return documentViaDB;
        }

        return Optional.empty();
    }

    public void delete(PersonId personId) {
        iPersistDocumentsViaDB.delete(personId);
        iPersistDocumentsViaCloudStorage.delete(personId);
    }
}

Finally, the Configurer:

ManageDocumentsFromREST = manageDocumentsFromREST =
    new ManageDocumentsFromREST(
        new ManageDocuments(
            new PersistDocumentsViaDispatching(
                FeatureFlagAPI,
                new ManageDocumentsViaDB(),
                new ManageDocumentsViaCloudStorage()
            )
        )
    );

The feature is enabled via FeatureFlags for DB and CLOUD_STORAGE possibly in this order:

Each stage would be active for a few days, weeks or even months, with observation to ensure that it’s working correctly.

I added one invariant confirmation observation and notification in the implementation above. If a Document is not found in Cloud Storage, but it is in the DB and the DB has been disabled, then return the DB’s Document and notify someone that there’s an inconsistency.

Other invariant confirmation observations and notifications may be desired, but I only provided this one as an example.

Once all Documents have been migrated, then there’s no need for ManageDocumentsViaDB or even PersistDocumentsViaDispatching. We’d end up with a design that looks like this: Basic Hexagonal Architecture with Document with Dispatcher

Nowhere in this design transition did we need to touch the Framework Adapter or any elements inside the Red Hexagon.

Nested Hexagons

We’ve gone deep and wide. Now we’re going to dive inward. Can Hexagons be nested?

Let’s return to Alistair Cockburn, who posted this on X/Twitter in November 2023:

someone asked me today: “If you were to define what hexagonal architecture is not from the observations you’ve made of implementation variations over the years, what would you say?”

Here are the pages from the draft book that say as well as I can in just a few pages:

Ah! He’s working on a book, and he posted images of a few pages. On Page 4 of his post he writes:

What about nested hexagons?

As described in paper “Component + Strategy” in Chapter 1, you can nest the Component + Strategy pattern, assuming you really write the tests. You can have components within components, if you like, assuming you meet the criteria.

The Ports & Adapters pattern is really aimed at protecting your team from external technology shifts. The pattern suggests declaring the system boundary just in front of each external technology. See “Where do I put the ‘app’ boundary?”

Thus we say that the Hexagonal / Ports & Adapters pattern does not nest.

I think Cockburn is being too restrictive. As I stated above, I think he views the Port as the edge of the system. I view the Port as the edge of the Business Logic. While Cockburn doesn’t think the pattern nests, I do.

I considered how to represent nested hexagons. The diagram was going to get way to cluttered. Is what did I even consider nested hexagons?

I often think about my blogs while I drift to sleep. While dozing off one evening I had a thought. My relationship lines leaving my Purple Hexagons always point outward. Cockburn doesn’t address this at all. Martin gets it backwards.

Purple Hexagons are Pure Unstable/Flexible elements. But they are not the only Pure Unstable/Flexible elements in Hexagonal Architecture. Others include:

Except for their creation, they are invisible to the rest of the design. All of these elements have an Event Horizon much like Black Holes. We can’t peer into them. This was a bit of an Aha! moment for me. It was there all along, and I hadn’t noticed it before. Purple Hexagons can be elements within Purple Hexagons. They are self-referential too.

A picture is worth a thousand words. All Pure Unstable/Flexible elements can be Purple Hexagons too. Except for creation, all dependency/knowledge arrows point outward.

Basic Hexagonal Architecture with Nested Hexagons

This is a fractal design. At this level of abstraction, we know what each internal Purple Hexagon must do, but we don’t know nor care how it’s done. It could be one class, or it could be many classes. There could be another Red Hexagon eco-system or a completely different design inside. The fractals could descend even further with more Purple Hexagons nesting within these Purple Hexagons.

If the External Dependencies are honored, the developer has carte blanche with respect to the design and implementation within the Purple Hexagon.

Let’s revisit the Dispatching example, but with a nested hexagon:

Dispatching Hexagonal Architecture with Nested Hexagons

The only difference is the addition of the PersistingConfigurer as well as adding the Purple Hexagon. Please remember that Hexagons, regardless of their color, are design boundaries. They do not exist in the implementation. They help organize the design and restrict the flow of dependency and knowledge.

Nothing has really changed except for the Configurers, and I think they will be easier to manage. PersistingConfigurer:

class PersistingConfigurer {
    public static IPersistDocuments getDocumentPersister() {
        return new PersistDocumentsViaDispatching(
            FeatureFlagAPI,
            new ManageDocumentsViaDB(),
            new ManageDocumentsViaCloudStorage()
        );
    }
}

Then the main Configurer is basically this:

ManageDocumentsFromREST = manageDocumentsFromREST =
    new ManageDocumentsFromREST(
        new ManageDocuments(
            PersistingConfigurer.getDocumentPersister()
        )
    );

Separation of Concerns

These designs are highly modular. This was hopefully evident as I was swapping and adjusting Adapter configurations. Except for creation, the classes don’t depend upon or know about one another. They only depend upon interfaces.

The separation of concerns means that different developers and different teams can work on the implementation simultaneously without interfering with one another if their shared interfaces are reasonable stable. All implementations are encapsulated within their Event Horizons. If they honor their dependencies, any internal design and implementation is possible since it’s invisible to the rest of the design.

Unit testing should be relatively easy to set up since there are no tight couplings. Test doubles can be provided for all dependencies.

Who should be working on what and when? There are many ways to approach this. Here are some of my personal thoughts:

Summary

These techniques allow us to expand in breadth and depth at any time. It’s not one or the other. We may not need this level of flexibility often, but when we do, it’s good to have these tools in our toolbox.

References

Here are some free resources:

Here are some resources that can be purchased or are included in a subscription service:

See previous blog References.

Comments

Previous: Hexagonal Architecture and how it compares to and contrasts with Clean Architecture

Next: Bumper Sticker Computer Science and Software Engineering

Home: Design Pattern Evangelist Blog