Achieving the Flexibility/Maintainability quality attributes in WCF Services
Manager, Engine, Gateway, a SOA Model
This article is the second in a series that offers practical advice to the creators of complex software systems on how to achieve the non-functional or technical requirements of their project.
SOA books describe the methodology, and the technology but seldom do they give any practical advice for building these types of systems. Here is some real no nonsense advice from the trenches for development.
Flexibility is the ability of a system to adapt to varying environments
and situations, and to cope with changes to business policies and rules.
A flexible system is one that is easy to reconfigure or adapt in response to different user and system requirements. The intentional separation of concerns between the manger and engine components aids flexibility as only a very small piece of the system is affected when policies or rules change. Engines encapsulate rules, and mangers workflow, so if the how we do something changes, only manager’s change, if the rules around the process change, only engines change.
As a general heuristic we say that engines and gateways should be reused. This means that the strategy and the data access should be written once and reused as much as possible. It is then possible to reconfigure the solution per use case (manager) by picking the engines and gateways required.
This becomes productive when you need to provide new functionality, which is conceptually just more use cases, that use the existing data in new ways, you only need to write a new manager and reuse the gateways and engines. If you need to reuse a manager, it means you have two identical use cases, eliminate one and delegate functionality to the existing service, never copy and re-host a manager.
This is the reason we introduced the concept of the aggregate root and a gateway per contract, so that even if you require a different view of the data you will be able to aggregate existing gateways to produce the contract that you require.
Maintainability is the ability of a system to undergo changes to its
components, services, features, and interfaces as may be required when
adding or changing the functionality, fixing errors, and meeting new
business requirements. The maintainability of a system is dependent on
its ability to manage complexity.
In reality as it turns out, it is not complexity that causes maintenance headaches, but rather how the complexity is sequestered or partitioned. Complex code that is well partitioned and lives in one place only is easy to manage, and when a simpler method is found it is easy to remove and replace. Complex code that is scattered over many places with multiple dependencies to other pieces of dependent code equals maintenance nightmare.
So the question is; how to partition our application so that we can avoid complexity from spreading. Luckily the answer lies in the realm of set theory and equivalence relations.
In order to manage complexity we must first understand it, the complexity of a system is a function of the number of states in which a system can find it’s self.
An example is a system (A) that has 3 dice, so the number of potential states is 63 or 216 potential states. Another system (B) with one dice has 61or 6 potential states, if you where to make repeated guesses for both systems, you would be right, on average 36 times more often with B than you would be with system A because system B is less complex and easier to predict.
With this basic model of complexity, we can gain some insight into how complexity can be better organized. Consider another two systems (C) and (D) both have three six sided dice, but in C all the dice are together as before, but in D the dice are divided into three partitions. Let’s assume we can deal with the three partitions independently, in effect, three subsystems each like B. We know that the complexity of C is 216. The overall complexity of system D is the sum of the complexity of each partition (61 + 61 + 61) or 18. If you are not convinced of this imagine inspecting C and D for correctness in C you would need to examine 216 different states, checking each for correctness. In system D you would need to examine only 6 states in each partition.
The above table demonstrates how much more complex a non-partitioned system of 9 dice is compared to a partitioned system containing the same number of dice. Ratio of 10,077,696 to 54, a lot!
How then do we partition a software system so we too can gain the advantage from this property of the universe?
A partition is a concept from set theory; a partition is a set of sub sets that divide a larger set so that all the items in the larger set live in one, and only one of the subsets. Like our dice example each die lives in one and only one bucket. One can also observe that in any set of items there are at least N possible ways to partition that set.
In software systems it just isn’t enough to just partition variables into different subsets; we need to find one that honours the dependencies between the variables.
This is where equivalence relations become useful. An equivalence relation is a Boolean (true/false) binary relation that for any elements, say a, b, c in the same set the following three properties are always true:
E(a,a) is always true – reflexivity
E(a,b) always implies E(b,a) – symmetry
E(a,b) and E(b, c) always implies E(a, c) – transitivity
Consider a small shop, all the items in the shop can be considered as being in the same set, if we pick the equivalence relation costs the same as let’s see how this becomes useful.
The shop stocks Cereal for R10.00, Pens for R10.00, Coke R10.00, and Notebooks R20.00.
Reflexivity – costs the same as (Pens, Pens) = true
Symmetry - costs the same as (Cereal, Pens) = true and costs the same as (Pens, Cereal) = true
Transitivity - costs the same as (Cereal, Pens) = true and costs the same as (Pens, Coke) = true and costs the same as (Cereal, Coke) = true
And to check our logic costs the same as (Cereal, Notebooks) = false.
Thus because for a particular equivalence relation every item in the set lives in only one partition we can use equivalence relations to formulate partitions. In formulating architecture the equivalence relation that is of most interest to us is synergy, two functions are synergistic when one requires the other to be effective, if you can imagine a situation when one can be used without the other they are said to be autonomous.
Equivalence in Practice
In practice we take our system and pull out all the required functionality for example in a retail operation use cases could be:
Calculate total cost,
Charge credit card,
Remove from stock,
If we apply the synergy test we can we that Calculate total cost is synergistic with Calculate change, it is hard to imagine one without the other, in our retail set. At first glance Charge credit card looks synergistic with Calculate total cost, but remember symmetry, it is a two way street; it is possible to imagine a scenario where you would Calculate total cost without charging credit cards (cash payment for example), therefore Charge credit card is actually autonomous to Calculate total cost.
Another property of equivalence relations is that although the inverse of an equivalence relation is never itself an equivalence relation it has some interesting mathematical properties of its own. If we denote E as an equivalence relation and its inverse as ~E, if E was used to generate a partition, the following could be said:
If a and b are in the same equivalence class of E then ~E(a, b) is always false, and if a and b are in different equivalence classes of E then ~E(a, b) is always false.
For example, costs the same as (Cereal, Pens) = true, think of this as E(a, b), and does not cost the same as (Cereal, Pens) = false, then this is ~E(a, b) because these two items are in the same equivalence class of items that cost ten rand. Similarly ~E(Pens, Notebooks) = true.
This leads to a useful trick, as you can see it is quite difficult to generate such a set where does not cost the same is false for all elements within a set and true for all elements across sets, but if you realize that does not cost the same is the inverse of costs the same the exercise becomes trivial and you will automatically have the property of non-equivalence for does not cost the same. So why is all of this so important? Well in designing service orientated applications synergy is not a particularly important property, but its inverse autonomy is a very important one, so important it is included as a SOA tenant.
Five Laws of Partitions
So with this incredibly useful method to partition application
functionality we also get the best possible partition and achieve the
tenet of autonomy, our system is a small as it needs to be no smaller.
The rules to formulating partitions can be summarized into five laws:
- Partitions must be true partitions. Items live in one partition only, ever.
- Partitions must be appropriate to the problem at hand. Partitions only minimize complexity when they are appropriate to the problem at hand, e.g. a clothing store organized by color would have little value to customers looking for what they want.
- The number of subsets must be appropriate. Studies show that there seems to be an optimum number of items in a subset, adding more subsets, thus reducing the number of items in each subset, has very little effect on complexity, but educing the number of subsets, thus increasing the number of elements in each subset seems to add to complexity. The number seems to sit in the range 3 – 12, with 3 – 5 being optimal.
- The size of the subsets must be roughly equal. The size of the subsets and their importance in the overall partition must be roughly equivalent.
- The interaction between the subsets must be minimal and well defined. A reduction in complexity is dependent on minimizing both the number and nature of interactions between subsets of the partition.
By reducing complexity and ensuring that our applications are well partitioned and autonomous we effectively sequester complexity and make our systems maintainable