Architecting for Change

How to Decompose Systems by Volatility

Charlie Koster
9 min readFeb 17, 2024

Every software system has an architecture whether intentionally designed or not. While there are many architectural styles on offer, hands down the best approach I’ve employed is Volatility Based Decomposition, an approach inspired by Righting Software.

This post describes an interpretation of the method described in Righting Software focusing on decomposition by volatility as the north star.

Volatility Based Decomposition

The essence of volatility based decomposition is the to answer the question “What, if changed, wouldn’t I want to become a rippling change across multiple systems?”

An attempt to depict a noticeable change in one system does not impact another

There are obvious examples of encapsulating change such as abstracting away an integration with a third party vendor, however the right frame of mind is important in order to avoid alternative approaches like Functional Decomposition or Domain Driven Design which may sound good in theory but ultimately do not prevent changes from rippling across systems.

Broadly speaking there are a few activities involved when decomposing systems by volatility:

  1. Identify core and supporting use cases, the behavioral essence of the system.
  2. Identify volatilities and subsequently prescribe the component or strategy for encapsulating each volatility from leaking outward.
  3. Validate the architecture by producing call chains. Any system components that aren’t in any call chain are unnecessary.
  4. Make note of non-obvious details. Boxes and arrows can only communicate so much.

Identify Use Cases

Let’s scratch the surface of a system we designed and have used in production for the past two years, a Payment Processing System.

What should a system that encapsulates payments do? Perhaps more importantly, what should it not do?

Contrary to the opening line in this section it’s often better to not start with the name of the system. Rather, start by throwing use cases at the wall. Verbs and nouns. Get them out there.

  • Pay invoice
  • Pay Bill
  • Delete payment
  • Set up bank account
  • Change bank account

Continue throwing out ideas for potential use cases, scrutinize them, and iterate. Consider both current and foreseeable future use cases. Avoid functional terms and instead focus on business behaviors. For example:

  • ❌ Delete a payment -> ✔️ Void payment

Avoid leaking specific business entities that the system shouldn’t need to know about. For example, if something fundamental changes about Invoices or Bills should a system that processes payments change? Nope!

  • ❌ Pay invoice/bill -> ✔️ Make payment

So far these are user-driven use cases. Don’t forget system-driven use cases.

  • Update payment status
  • Process refund/chargeback

Lastly, group these more granular use cases and categorize them into a few buckets. These buckets represent core use cases, the essence of the system, and likely inform what you might want to name the system.

Core use case: Manage payments
Supporting use cases:
Make payment, void payment, process refund/chargeback, update payment status

Core use case: Manage payment methods
Supporting use cases: Create payment method, change PM, remove PM

For brevity I’ve omitted a few use cases although I’ve captured enough to give a sense of what this system encapsulates. It’s a payment processing system. It encapsulates volatilities involved with moving money from Point A to Point B and it is shielded from other volatile areas of the business that aren’t strictly related to moving money.

Note: At Buildertrend we’ve found it helpful to distinguish between product systems and supporting systems. A product is a system that a user logs in to, interacts with a UI, and manages user permissions and preferences in addition to other use cases. A supporting system is agnostic and reusable across multiple product systems and often interacted with via an API. This payments system is the latter variety.

Identify Volatilities

What, if changed, would we want to encapsulate and prevent from rippling to other systems? I touched on this briefly in the prior section but I recommend calling out volatilities and encapsulations as a separate exercise to make them explicit.

Volatile vs Variable:
Not everything that changes needs to be encapsulated. A common example of variability, as opposed to volatility, is a status value. Status enums do change but these types of changes are easily handled with code. I would not recommend encapsulating the changeability of something like payment status.

A few payment processing volatilities and how they’re encapsulated:

  • Third party vendors / Resource handler in code
  • Use case sequencing / Manager in code
  • Payment method specific decisioning / Engine in code
  • Transactional fees / Pass the fee into the API
  • Credit card entry / Iframe wrapper

The first three volatilities are encapsulated using specific classes and layers at the level of code. The terms resource handler, manager, and engine all come from Righting Software and the names aren’t terribly important. What is important is the prescription to protect changing a third party vendor from rippling to other areas of the code, from keeping the decisioning for ACH vs credit card from rippling, or changing the order of events for specific use cases.

The final two volatilities are really interesting and worth diving into deeper.

Transactional fees
If the business wanted to conditionally change transaction fees based on business rules on the invoice, or whether the transaction was a rebate disbursement, or whether the user was in the premium subscription tier should a payments system care? Should we be tempted to teach a payments system what an invoice is, a rebate, or what a user + monthly subscription is?

No. These are volatilities in other areas of the business.

The volatility encapsulation in this instance is simple. If the business wants to change the fee on any given transaction for any reason then great! Pass in the fee you want when invoking POST /payment. No need for a code change in the payment processing system to accommodate fee changes.

Credit card entry
Some third party payment processors who offer credit card payments allow you to embed their PCI compliantly hosted iframe in your application. However, if we simply expose PaymentVendorA’s iframe in the consuming product and then we later change to PaymentVendorB that will require a coordinated code change across multiple systems. In other words, it’s a volatility that lacks encapsulation.

If, on the other hand, we wrap PaymentVendorA’s iframe with another iframe owned by the payments system, and create a standard contract between the iframe wrapper and the parent window, then the consuming product system can embed the iframe wrapper and not require a code change if PaymentVendorA changes to PaymentVendorB. The volatility of the payment vendor’s iframe is completely encapsulated.

Call Chains and Architectural Validation

We finally get to what is often the starting point in other architectural approaches: the boxes and arrows.

The purpose of this next step is not to exhaustively document the system in seemingly academically sound ways. Doing so is often overkill and even counterproductive as there is a point quickly reached when more documentation makes the reader less likely to read it.

Two cents on good documentation:
Good documentation is read and understood by its intended audience. I’ve found the best way to accomplish that is to be concise, be accurate enough (but not exhaustively so), and iterate. Ask a colleague to read your documentation and validate whether it was read and understood by them.

Call chains
The purpose of this step is to ultimately work towards validating your architecture. How do you know when you’re done?

The short answer is once all current and foreseeable future use cases are represented by call chains, and each system component is used in at least one call chain, then you’re done with designing this system.

To do this we need call chains!

Let’s begin with putting relevant system components on the screen. If something is encapsulated in code I make a point to ensure it is represented.

Component diagram for a payment processing system

The next step is easy enough. For each supporting use case connect the boxes with solid (sync) or dotted (async) arrows. Here are two example call chains:

Make Payment Call Chain
Update Payment Call Chain

After depicting each call chain for use cases current and foreseeable identify if any system components are orphaned and have no arrows in or out. Any extra managers, engines, or caches? If yes, those are superfluous.

Once all use cases are represented by the minimal set of system components the architecture is validated.

Noted Special Considerations

What is unique about this system that is worth mentioning explicitly? I’ve found this to be an incredibly beneficial step to help convey what diagrams and bullet points cannot.

The two examples in this system were mentioned above: nuances with transactional fees and the use of an iframe wrapper.

Another system I’ve designed is a Media System, the essence of which is to encapsulate volatilities for storing, managing, scanning, and overall handling photos, videos, and other media.

You’ll likely be able to guess at many of the verb+noun pairs that make up its behavioral use cases. That system also had some interesting volatility encapsulations that I documented separately as special considerations.

  • Virus definitions change after a file has been scanned. This informed another use case for rescanning files while remaining agnostic to the use case trigger, whether scheduled or by polling/webhooks.
  • Signed URLs are a way to directly publish or retrieve files to and from a datastore. Unfortunately some vendors differ in implementation. By carefully designing the API we found a way to encapsulate that volatility.
  • File templates are a common product feature, however, at the level of a reusable system that manages files it’s an unnecessary concept. A file template is just another file and there is no need to introduce constraining opinions on how every consuming system should treat file templates. In other words, we left file template volatilities as the responsibility of the consuming systems.

Next Steps

What comes next is where we depart volatility based decomposition and enter organization-specific practices and processes for building software. I’ll leave a few notes for what has worked well for me when taking the system from designed to implemented.

Partner with the Lead Developer and sketch out the API contracts, even going so far as to put it in code with mock responses. Building to a mocked interface is an effective way to parallelize work and incrementally build the system. It also has the convenient side effect of gathering momentum on building out the system. It builds excitement around a green field system.

Similarly, put some low resolution DB schema sketches together. What are the main entity concepts in the system and how do they relate? How will you design for multi-tenancy? Are there performance considerations to anticipate at the level of the database? Like the prior point, this is a momentum builder that makes the system begin to feel real.

Maintain focus on delivering customer value. Contrary to the book’s advice here, iterative value delivery to the customer is most important above all else. When we originally built the payment processing system at Buildertrend we went to production without implementing the use case for programmatically voiding a payment. With the beta group we rolled out to it was feasible for users to call in and for Support to manually void payments in the vendor’s UI.

We of course did implement voiding a payment within a few more weeks. However, had we followed the book’s project design prescription then customer value and feedback loops would have been delayed 4–6 weeks longer than necessary.

Of note, if you do go pick up a copy of Righting Software I can’t help but point out the similarities between the book’s advice on project design and waterfall. If you’re familiar with the noises I generally make on being agile you won’t be surprised at my suggestion to ignore that part of the book.

Your mileage may vary.

And with that I’ll conclude this interpretation of Volatility Based Decomposition that has really worked well for me as a Principle Software Architect.

Reach out with any questions! It’s a really interesting topic to discuss.

--

--

Charlie Koster

Principal Software Architect | Conference Speaker | Blog Author