“None of us is as smart as all of us.” Ken Blanchard
The fifth and last principle of the FLUID Methodology is Decentralised Control: moving the locus of the application logic where the context information is readily available.
When writing a complex application, it’s tempting to place all the business logic at top level. Behaviour Driven Development (BDD) and Business Requirement Documents (BRD) are easily translated into a set of high-level instructions or function/method calls.
For example, consider the business description of how to print an invoice, applying discounts based on the special status of a customer. The meta-code implementing this business logic may look like this:
function PrintInvoice(invoiceID, customerID):
items := listOfItemSold(invoiceID)
prices := pricesForItems(items)
# Directly encode here any rule determining the special status
if hasBought(customerID, date().year) > specialCustomerThreshold():
prices = applyDiscounts(items, prices, customerID)
end
... other instructions to produce the invoice...
end
In this example, the decision whether to apply a discount or not is at the highest level of implementation, mapping a BDD/BRD document describing the business logic.
The problem with this approach is that the context required may or may not be easily available at this level in the application. In the specific example, the database used to determine the status of the customer and/or the discount levels may or may not be part of the data readily available to PrintInvoice, and if not, propagating it could cause data pollution, semantic confusion and in general code spagettification.
In FLUID, we can solve the problem by decentralising the control: delegate the decision of how to treat the prices in this invoice to a lower level component.
Control Decentralisation
Decentralising control is about empowering the low level components having more intimate knowledge about the local context with the control of the overall status and behaviour of the application. According with this principle, and continuing to use the larger definition of component we adopted in FLUID:
Decentralisation of control means that Business or application logic should be driven by the components having the least distance possible from the required context information.
In practice, this means creating “self-aware” low level components that can pilot higher level logic by locally taking relevant decisions, which the top-level application takes for given and acts upon.
In the above example (determining the discount for special customer), this can be implemented in two ways, both requiring to write a Customer component extending the customerID concept:
- The Customer component may have understanding of discount levels, so that the higher level logic just asks it for the discounts to apply; or
- It may provide enriched/additional information needed for the user code to take the business decision of applying a discount at a higher level.
Business Logic Distribution
The best solution for your specific application could be either of the options above.
There is merit in keeping the business logic together in the code, easily rendered as sequence of operations that map into an existing formal document; as “one size doesn’t fit all”, the distribution of the business logic across application level depends on many aspects of a project.
However, notice that even if the logic to apply a discount is kept at the higher level, the improved Customer entity is still supposed to “know” what information to store and expose for the higher level logic to work.
This causes to write the Customer component taking into account some hidden assumptions still based on the business logic. Doing so, those assumption become implicit and are spread across all the chain of control.
Conversely, moving coherent parts of the business logic in lower-level components allows to keep all of it explicit albeit broken across multiple components.
Control Decentralisation and Separation of Concerns
On the surface, the SOLID principle of separation of concerns / single responsibility appears to be very similar to the FLUID principle of decentralisation of control: both suggest that lower level components should be dedicated to handle specialised parts of the application logic.
However, the difference of the FLUID model appears evident in the focus on the control aspect. In a FLUID application, decentralising the control doesn’t mean to identify and isolate separate concerns of the application domain; rather, it suggests delegating the control of part of the application to the components that naturally handle the information required for that control to take place.
In our example, the FLUID compliant Customer entity would “know” when a customer is deserving a discount, and possibly even provide an interface to obtain the actual discounted amounts. Nothing is said about separate application concerns as i.e. storing or retrieving the customer data from a database, or handle inconsistent states and exceptions (although other FLUID principles suggest to group them under the same code), and a strict interpretation of the concept of separation of concerns may suggest having this controls moved in a separate, business-logic specific entity.
Bottom-up Focus
A side effect of the decentralisation of control principle is that the development process encourages bottom-up development.
As control is moved in detail components, more development takes place at lower level, near the areas of the code that have full context necessary for the detail decisions to be handled.
Delegation and Continuous-delivery
As coding progress from the bottom, AGILE practices as Extreme Programming and TDD become easier to implement. Marginal changes to business and application logic target a more focused part of the application, with all that involves in terms of smaller updates shipped through continuous delivery systems.
The time-to-market is also positively impacted through a verify-first, fail-fast approach, in line with the AGILE manifesto. As we’re giving control to decentralised components, the feasibility of the necessary controls through the available context is tested sooner. If some information is missing, or the data structures are not organised optimally for a specific part of the business logic to be easily implemented, that appears self-evident at earlier stages in the development cycles, and corrections come sooner and easier.
Semantic Simplification
Another side-effect of the decentralisation of control is that the business and application logic must be broken down in modules that must be self-coherent. Paradoxical or inter-dependent logic issues are made evident early in the process, and working on the design documents is made simpler by modularisation, which becomes not just useful but even necessary in order to decentralise crucial business logic parts.
In our example, the Customer entity must have direct access to all the necessary information for determining if a discount is to be applied or not. Imagine a situation in the business logic where the discount can be applied also to, say, first time customers participating in a promotional entry offer.
If the business logic for discounts were fully implemented in the higher level code, it would be tempting to perform an out-of-band query on a separate database, and consider the result extending logic locally. As more of these ad-hoc changes are required through time, it’s easier to see how the code can become spagettified.
Conversely, when it’s necessary to decentralise the control of the discount logic in the application, also the business logic need to clearly identify the concept of discount. In the above example, it’s now necessary to reformulate the business logic so that the promotional offer becomes “natural part” of the “discount decision flow”.
Decentralisation of Control and AGILE programming
Going past the “corporate-friendly” AGILE implementations (i.e. Scrum) and referencing the original AGILE manifesto, the fact that components own their share of the business logic merges seamlessly with the way AGILE envisions the practical aspects of writing applications.
The ability of “writing applications (as if) sitting with the customer” relies on all the relevant code being “at hand”, with all the logic that could be influenced by a single change identifiable as easily as possible.
Decentralising the control, that is, storing the business and application logic in the lower level components helps ensuring that the application logic and context it needs “stay together”, thus helping development, refactoring and improvement as envisioned in the AGILE manifesto.
Conclusions
Decentralisation of Control means moving the application-level decisions in the lowermost components that have access to the full context needed. It may also require to redesign or iterate on the business logic so that the required context can easily be accessed by lower level components.
This helps avoiding the spread the information required to implement the application logic to multiple layers and components, reducing “data pollution” across components and interfaces.
Applying this principle correctly, that is having self-coherent components, simplify the adoption of AGILE development, TDD and CI/CD principles.