Clear separation between internal and external domains the Propeller way

Anthony Sceresini
Life at Propeller
Published in
4 min readMar 30, 2022

We are Propeller Aero Engineering! A startup engineering team based (mostly) in Surry Hills, Sydney, that develops in-field GNSS hardware and cloud software that records, ingests, processes and analyses very large geospatial and time-series datasets. This positions us with unique challenges and opportunities as our engineers own the ‘full-stack’ of the IoT.

The following is a training document that we have as part of our internal resources for our engineers and forms part of our series 101 Engineering: Technical Best Practice. We always tie back technical best practices to internal systems to provide relevant examples for our engineering team.

When building an evolvable system, a system that can more easily change over time, there are several important considerations to keep in mind as you build said system. One consideration is of keeping a clear separation between a system’s internal domain and everything else, the external domain.

A domain, as defined in Eric Evans’s Domain Driven Design, is: A sphere of knowledge, influence, or activity.

Therefore, the internal domain of a system can be thought of as the knowledge, influence and activity that a system is directly responsible for and has direct control over. Inversely, the external domain of a system, can be thought of as the knowledge, influence and activity a system is NOT directly responsible and has no control over.

An external domain can be vast and varied and under considerable flux, which often is at a different pace to that of any one system reliant on various aspects of that external domain. It is desirable to build a system that is minimally impacted by its external domain, where the cost of change incurred by the external domain is driven down, in-turn increasing a systems evolvability.

We can achieve this goal of clear separation by leveraging aspects of Hexagonal architecture, namely, adapters.

Like other applications of adapters (e.g. power adapters), an adapter is something that we build at the edge of our application that defines an interface between a system’s internal and external domain and encapsulates the implementation of that transformation (adaptation) through an understood contract.

Use case

A well-understood use case is that of feature flagging, a system’s desire to understand when to enable or disable certain behaviour/functionality.

Feature flagging is a well understood, common requirement within software development and therefore it will generally be managed by a 3rd party system, in Propeller’s case (at time of writing) it is managed by LaunchDarkly. LaunchDarkly will provide a client library for many widely used languages/frameworks such as Python and NodeJS or at the least an HTTP API Interface.

A common approach

Commonly, LaunchDarkly will be implemented like so, with no separation between internal and external domains:

Implementing LaunchDarkly with no clear separation between domains
Implementing LaunchDarkly with no clear separation between domains

Strewn throughout the system, there will be direct invocations on an initialised LaunchDarkly client library. If there was a desire to change feature flag providers from LaunchDarkly to FlagSmith, the above system would incur the following change:

The cost of change with no clear separation between domains
The cost of change with no clear separation between domains

Change would occur throughout the system as FlagSmith introduces a new interface. The cost of this change is quite high, with change occurring across many layers of the system, requiring all changed code paths to be tested.

Adapter approach

Utilising the adapter approach, like so:

Implementing LaunchDarkly with clear separation between domains
Implementing LaunchDarkly with a clear separation between domains

A clear separation has been made between the internal and external domains of the above system. A FeatureFlag adapter has been implemented which affords the following:

  • Language that fits the internal domain can be used, devoid of language enforced by an external domain: featureFlag.canUploadBigTiff() vs ldClient.variation(‘system-a-can-upload-big-tiff’).
  • Implementation detail, including setup of options, userIds etc can be encapsulated within the FeatureFlag adapters contract to the internal domain and its understanding of how to handle that request as opposed to duplicate setup being performed at every invocation site.

If there was a desire to change feature flag providers from LaunchDarkly to FlagSmith, the above system would incur the following change:

The cost of change with clear separation between domains
The cost of change with clear separation between domains

The change is kept solely within the FeatureFlag adapter. Another advantage is that other patterns such as Branch by Abstraction can more easily be implemented to incrementally rollout a change, if there is desire.

As change is constrained to the one adapter, testing effort is also reduced.

Conclusion

Architecting a clear separation between a system’s internal and external domains will increase the evolvability of a system by decreasing the cost of change incurred by change within a system’s external domain as change is constrained to one part of the system.

Additionally, the external domain’s language need not leak into the system’s internal domain.

This approach can also be utilised within a system’s internal domain, to create a clear separation between the domain layer and other layers within a system, such as the data layer.

References

If you are interested in hearing more about what we engineer at Propeller Aero and our unique challenges as we scale please reach out to us directly on LinkedIn or check out our current open positions.

--

--