An old but still valid principle for software designPosted by Daniel Moisset 1 year ago Comments
In this article I want to talk about an old software design principle and how it can be applied even in modern software. it is unusual to see a principle that has kept itself current for so long and that many good engineers ignore. Even in some situations that I have seen it properly applied, it has been done after analyzing the concerns for the specific situation although instead of just knowing that the concerns are general. That made me think it’s a good idea to write a bit more about it, while updating the examples to more modern scenarios.
So what is it?
Software usually is a complicated “thing” that has to solve a varied range of issues and provide several interrelated functions. Software design is the art of drawing lines inside that thing that split it into several pieces, turning a complicated “thing” into a system. I’m calling it an art more than engineering because we engineers haven’t agreed yet on accurate and quantitative metrics to characterize a good design, i.e. how to objectively measure if a given line is good or not. The best we have is some subjective indication that a design is bad when certain production activities (adding features, diagnosing and fixing bugs, introducing new people to the system, etc.) turn out to be harder than the “intrinsic” complexity of the problem would lead to believe.
Even if the discipline is an art, it doesn’t mean that there aren’t principles or guidelines to follow, and knowing them help us being more successful as designers. There are several intuitive ways to split a system into pieces, for example splitting by features or by important entities in the problem domain. These intuitions provide useful ideas in many cases (though not all of them) but they usually omit something that it is not obvious at first glance but normally useful: the distinction between mechanism and policy.
This distinction was usually applied in operating system designs, probably because that’s the area where “hot” discussions about software design happened in the 1970s, but it applies to a really wide range of systems. In essence it proposes two concepts:
- a mechanism is the part of the software that performs an activity, may be computational (making a query on a data structure, running an algorithm) or interact with some hardware or external system (saving a file, displaying a picture, making a phone call, starting a rocket engine).
- a policy describes rules that control the mechanisms: under which conditions they can be used, which sequences of actions are valid and which are not
Once you have this distinction made, a line is drawn between the system pieces that implement them. The design principle suggests some details of what should happen:
- The mechanisms and policy should be implemented in separate components (modules/classes/units)
- The policy uses the mechanism, but the mechanism is independent from the policy
- The mechanism should not restrict much what policies are implementable (this is an ideal which can only be achievable in different degrees)
So, let’s see it in action:
Once you’ve seen this distinction it should feel quite obvious, but it’s one of those things that are hard to see without a few examples, so let me go through a few:
- Let’s say you have a user-triggered action like submitting a post on a social application. That action may involve one or more mechanisms (saving the data into the database, possible sending an update to consumers of that content). The module/code implementing those mechanisms should be separated from the one implementing policies, which is the code that implements validation and flow between those mechanisms: which user can post content? what are the validation constraints on the content? who can see the content? when are those people notified?
- Database engines (both SQL and non-relational) have succeeded as good abstractions in part because they enforce this separation into the implicit application architecture: they provide mechanisms (for persisting and querying data), and the database client application defines policies for using those (what’s the schema, when to save information, which queries to make).
- The examples above shows that this principle can be applied on several layers at once. In fact, the most classical example of layered architectures, protocol stacks, are multiple applications of this principle: Each layer offers something that looks like a mechanism to the upper layer, and defines policies (ordering, timeouts, rules) to use the mechanisms in the layer below it.
- If you’re implementing an authentication system, saying “we’ll provide two-factor authentication” is a policy decision, and should be implemented separately from the authentication mechanisms themselves (password requests, mobile app checks, smartcards...).
Of course the list is not exhaustive but it gives you a flavour of the ideas, and how widely they can apply.
... and counterexamples
To complement the list above I’ll mention a couple of examples of situations where I’ve seen this principle misapplied to also give some negative examples:
- I’ve seen web applications where an endpoint is implemented in a single function that takes care of ensuring that the user has authorization, that the data is valid, making some transformation on the data (for example rendering a template) and then sending it back to the user. There’s nothing wrong if the function just calls separate components to do each of these steps, but if the implementation of these steps is intermixed code is much harder to maintain and test. Some frameworks may help a bit here by providing adequate mechanism or policy abstractions (for example Django provides a Form abstraction where you can put validation separately).
- Every time you’ve seen a hardcoded configuration value (a path, a timeout, a retry counter) what you have is essentially a little bit of policy mixed with your mechanism (especially because I’ve usually seen those in code that also implements mechanisms). You already knew you shouldn’t do that, but now you have an additional explanation for why.
Why is this a good thing
In design discussion it’s important to make yourself these “why?” questions so you’re able to understand what are the benefits or trade-offs being made. The main advantages of the separation of mechanism and policy are:
- Mechanism and policy normally evolve at different paces: Mechanisms are often stable once implemented, and it’s more usual to add new mechanisms than to change existing ones other than bugfixing. They are usually tied to the more hard/scientific sides of programming and computer science and can be well defined. Policies are usually tied to the product definition, business goals, end users, or performance tuning. Those are environments that are not exactly defined and change over time so it’s good to have them in a separate context and being able to change them without destabilizing the foundational mechanisms.
- The kind of unit testing that they require are typically quite different. Tests for mechanisms are usually more value-based (check that some input values produce appropriate values), while tests for policies are usually more process-based (check that some input values produce or not certain sequence of actions). Checking for both in the same test can make your test unnecessarily complicated and hard to maintain (when policies change, which they do often). Policy tests are good places to use mocks (where a mock substitutes a mechanism).
- The separation allows you to use appropriate tools for each level. The code for mechanisms and policies can have quite different styles and concerns even if both are code. Typically performance constraints are found in mechanisms, while flexibility concerns are in the policies. So this separation can allow you to have a numerical core for your app in C, or a database engine in C++ as mechanisms, while the logic and policies end up implemented in a friendlier and more flexible language like Python or Ruby. If you separate mechanism and policy from the beginning you can later change more easily to separate processes and languages and the line is drawn exactly when you need it to be.
There has been some criticism to the principle, which I think is not against the principle itself but on some interpretations that have been done when designing systems. There have been some situations where for a given problem, someone designing a library/server said “I’ll implement only a mechanism, and the clients/API users will have to implement the policy”. As an idea it allows a simpler design, and in some situations it may work very well, like the database engine example I mentioned above.
But there is a risk in this approach, which appears when many clients/users have to interoperate through your system, and each of them has its own conflicting policies. If interoperation is relevant to your use case, it’s better to design at least some degree of “default” policy. This is of course much harder because it requires a clear vision of how your library/service will be used, which is unusual to have. The most infamous example of this is X, the graphic system that has been the standard in UNIX-like systems, that through an initial philosophy of “we’ll provide a mechanism but not a policy”, promoted a wide range of systems that had a very hard time interoperating in even simple things like copy&paste. It took several years of organizations defining standard policies outside the system (and implementing those as helper libraries) to put X into the level of coherence and usability comparable to other systems that were much more opinionated and rigid.
That’s why I wrote about “separating mechanism from policy” instead of “provide a mechanism but not a policy”: both have to be present, in separated components. The principle doesn’t tell you when it’s a good idea to have both components in your system, or when to leave one of them to third parties. That is another design decision that you’ll have to make yourself.