Configuring behaviour in Inversion
Or, Experiments with black-box development.
update: Part 2 now follows on with the “further work” outlined toward the end of this article.
I’ve recently overhauled both the way that Inversion configures behaviours and the way in which that configuration is acted on as selection criteria when determining which behaviours should respond to an event. I thought I’d write this up as it keeps the small group of developers engaged with this work up-to-date, provides some informal documentation, and provides an illustration of a couple of Inversions design goals.
You need to know where the goal is in order to score
TL;DR Make things as bendy, fast and easy to change as possible… Check that you are.
Inversion might have been labeled Application stack number seven as it sits on the back of six previous incarnations the first of which started in 2004 as an experiment in implementing MVC on the .NET platform, the decedent of which went live into production in 2006 where is remains to this day. Two other slightly earlier but very close incarnations of Inversion have gone into production in 2012 and 2014, but by this time the point of interest had moved well past MVC to playing with ideas of behavioural composition as a means of meeting cross-cutting concerns normally addressed by AoP.
So Inversion is merely the most recent of a series of application stacks experimenting with a handful of core ideas some of which are more developed than others at this point, but each of which should show progression rather than regression over time.
My experience tells me that any piece of development spanning more than a handful of weeks quickly starts accumulating the risk of it’s initial goals being diluted and then eventually forgotten. It is important then to remind ourselves what it is in broad terms we’re trying to obtain from a system, and then to review our activity and ensure we’re actually meeting those goals whether formally or informally.
This is a summary of some of Inversions goals :-
- Black-box components
- Configuration as resource distinct from application
- Favouring composition over inheritance for application behaviour
- Small footprint micro-framework
- Single responsibility
- Extensibility, DIY
- Substitution, Plugability
- Inversion of Control
- Testability
- Portability
- Conventions for state
- Speed
Behaviour configuration will have a large impact on Inversion and will almost certainly add a distinct flavour to the frameworks usage for better or for worse, so it’s important once we’re done that we review our goals and ensure they’re being advanced and not diminished.
Finding a common abstraction for behaviour configuration
TL;DR Tuples.
Inversion is in large part a composition of IProcessBehaviour objects. A set of condition/action pairs. The relevant portion of the interface being:-
|
|
When we context.Fire(event)
we pass the event to the condition of each behaviour registered with the context in turn. If the condition returns true, that behaviours action is executed on the context.
Over time we find a lot of common activity taking place in conditions.
- Does the context have any of these parameters?
- Do these parameters and values exist on the context?
- Do they exist on the event?
- Do we have these keys in the contexts control-state?
For want of a better phrase we call this the selection criteria of the behaviour.
So we quite naturally start to refactor common selection criteria into base classes. We also start to push out the specification of selection criteria to configuration of the behaviour.
In Inversions previous incarnation the expression of config for selection had gotten quite out of hand. Each category of test ended up with its own data-structure both to configure and drive that test.
|
|
This config would be injected in the constructor by the service container, and be acted upon by the behaviours condition.
|
|
It worked, but it wasn’t extensible in that to extend the functionality required adding more and more data-structures, with less and less common purpose. It put a lot of pressure on inheritance to both pick up config you were interested in, along with it’s condition checks. It was riddled with assumptions which you either accepted or were left with no functionality except a bespoke implementation. Special little snowflakes everywhere, which is the opposite of what is being attempted.
It worked in that it allowed specifying behaviour selection criteria from config but the expression of these configs in Spring.NET or in code, was painful, hard to understand, and messy. Worst it was leaking implementation details from the behaviours across their interface.
So I started playing around with something along the lines of IDictionary<string, IDictionary<string, IDictionary<string, IList<string>>>>
, which again kind of worked. It was an improvement in that the previous configurations for selection criteria could all pretty much be expressed with the one data-structure. The data-structure however was messy, and difficult to express in configuration.
Next I started playing with a structure something like, MultiKeyValue<string, string, string, string>
which finally started to feel in some way rational and self contained. I happened to be reading a piece comparing the efficiency of hashcodes between key-value pairs and tuples which made obvious a very short step to Configuration.Element : Tuple<int, string, string, string, string>
.
The class Inversion.Process.Configuration represents a set of ordered elements, or a relation of tuples expressing (ordinal, frame, slot, name, value)
. This is a very expressive structure, and with LINQ easy and efficient to query in lots of different ways.
The resulting configuration is easy to express in code, and encourages a declarative rather than fluent style.
|
|
Not the best notational representation ever of configuration, but not the worst, a definite improvement, and something it’s felt one can become comfortable with. It’s certainly a very concise configuration of a swathe of behaviour.
This also is not the primary means of configuration. This is showing the configuration of Naiad which is a toy service container Inversion provides suitable for use in unit tests. The above is the configuration of a test.
A good friend and former colleague (Adam Christie) is getting good results from the prototype of a service container called Pot, intended to replace the use of Spring.NET. Until that matures over the coming months, Spring.NET is the favoured service container for Inversion. This doesn’t stop you from from using whichever service container takes your fancy as IServiceContainer shows, Inversions own expectations of a service container are minimal.
|
|
If you can honour that interface (and you can) with your service container, Inversion wont know any difference.
What I mean when I say Spring.NET is the favoured service container is that out of all the possibilities, Spring.NET is what I happen to be focused on as a baseline.
Acting on configuration for conditions
TL;DR LINQ
BehaviourConditionPredicates provides a bunch of predicates as extension methods that take the form:-
|
|
Which illustrates extending IConfiguredBehaviour
for whatever condition predicates are useful over time, without having to modify IConfiguredBehaviour
or Configuration
. We establish our own convention of tuples, and act on them. In the above example we’re extracting elements from the configuration that have the frame and slot {"context", "match-any"}
which drops out the tuples:-
|
|
We check the context for the name
and value
of each tuple with ctx.HasParamValue(element.Name, element.Value)
.
You’re always free to write the conditions for your behaviours in whatever way you need. What we see here is only an illustration of how I happen to be tackling it.
Expressing tuples as XML
TL;DR Just read four nodes deep and call them tuple elements.
If you step back from XML a moment and consider it simply as an expression of a tree of nodes, there’s a trick you can pull with reading a tree of nodes as tuples which is a little novel in this context but which we take for granted when working with relational databases. That is databases that focus on the grouping of tuples into collections which we call relations, or more commonly tables… I’ll confess to that piece of conceit straight-away. I happened to be doing some reading on working with sets of tuples and ran across the fact that the relational in “relational database” refers to the fact that it’s the set of tuples that are a relation, commonly called table, not any association between tables as you might expect from the term. Was novel to me, and I now obviously like flaunting the term… Back on topic…
Given that our configuration is made up of a set of tuples the elements of which we’re calling (frame, slot, name, value)
, consider the following XML:-
|
|
If we read that one node at a time, and with each node copy it as an element of our tuple, our first tuple builds up thus:-
|
|
And we have our first tuple. Now if we were reading results from a database, we’d not be surprised if the next value value2
were preceded by the same elements, as they are unchanged. So our second tuple is {"context", "match-any", "action", "test2"}
. In this way we can read that XML snippet as:-
|
|
Which is exactly what we’re after. We can now define a set of tuples very expressively and in an extensible manner with XML, we now just need to hook this up with Spring.
Extending Spring.NET configuration
TL;DR Was much easier than expected.
I’ve been using Spring.NET since 2006 as the backbone of most applications I’ve built. It’s something of a behemoth, and the reality is I’ve only really ever used a very thin slice of its features. I’ve always been comforted by the fact that there’s a solution to most problems with Spring and if I needed to I could extend my way out of a tight space, despite the fact I’ve never had much call to.
One of the things I’ve always wanted to do was extend and customise Spring xml configuration. If you’re working with Spring and xml configs one of the costs is you’re going to end up with a lot of configuration and it’s got a fair few sharp edges to it. After having a stab at it I can only say I wish I’d done it years ago as it was far less involved than I expected.
The relevant documentation for this is Appendix B. Extensible XML authoring which lays out in pretty straight-forward terms what needs to be done. From this we produce:-
An XSD schema describing our extension of the Spring config
Which provides the schema for our config extension, and most importantly associates it with a namespace. This is our “what”.
There a small gotcha here. You need to go to the file properties and set Build action to Embedded resource, as you’re schema needs to be embedded in the assembly for it to be used.
A parser for our namespace
Which is responsible for mapping individual xml elements to the unit of code that will process them. For each xml element you register its handler, thus:-
|
|
This is our link between “what” and “how”.
An object definition parser
This is our “how”, where the actual work gets done. In these object definition parsers we process the xml and drive an ObjectDefinitionBuilder
provided by Spring.
If we consider a simple example of this implementation in ViewBehaviourObjectDefinationParser
. First we override GetObjectTypeName
.
|
|
When our element view
is encountered and ViewBehaviourObjectDefinationParser
is resolved from its registration for this element, Spring asks for a string expression of the type for the object that will be created for this element. We simply read this from the elements @type
attribute, exactly as Spring normally would.
Next we need to deal with any constructor injection, and it turns out that because we’re processing elements in our own namespace, elements from Springs namespace still work as expected, allowing us to mix and match to some extent.
|
|
Note the spring:constructor-arg
within the behaviour
element.
So we’re in the rather comfy position of retaining Springs base functionality in this area and merely adding syntactic sugar where it suits us.
Spring calls DoParse
on our definition parser, and passes it the associated element.
|
|
In this example we are extracting the @responds-to
and @content-type
attributes and adding them to the object definition builder as constructor arguments.
Reading the behaviour configuration from XML
Okay, so if we take stock, we’re by this point able to provide our own expressions in XML of object definitions. This doesn’t speak to our provision of a set of tuples as configuration for a behaviour.
BehaviourObjectDefinationParser is a little more gnarly than our definition parser for view behaviours, but it’s DoParse
isn’t too wild. We iterate over the xml nodes and construct a hashset of tuples from them, and once we have them we call builder.AddConstructorArg(elements)
to tell Spring that we’re using them as the next constructor argument.
|
|
Nothing clever happening here at all, left rather verbose and explicit to assist with debugging.
So we have our behaviours configurations nicely integrated with Spring, and with reasonable opportunity for extension.
Lastly from our behaviour.xsd
schema we can default attribute values for elements, as we do for message-sequence@type
:-
|
|
This allows us to write message-sequence
with it’s @type
value supplied by the schema.
The end result of these extenstions is the ability to express cleanly in XML the equivalent of our in code configuration of behaviours, as can be seen in behaviour.config
|
|
Which can be compared with a previous version. The difference is stark.
We can also see here both the beginning of our own domain specific language in the configuration of our behaviours, but more importantly the ability for other developers to extend this with their own semantics.
Consider the following definition of a behaviour:-
|
|
I just made that up, but hopefully it begins to become clear how that will be read as a set of tuples for the behaviours configuration that I can act on. You can make your own stuff up, which is what open for extension means. The ability for you to make stuff up, that I didn’t foresee and without you having to ask me to modify my stuff.
There’s a strong smell of Prolog around here now. If you’re familiar with Prolog, think of assertions upon which predicates act.
A little caveat on reading XML as a set of tuples
In a relation of tuples you can’t have a duplicate tuple, so tuples that are repeated are collapsed down to the one tuple. The consequence of this is you can’t do…
|
|
As you’ll end up with just the one {"fire", "work"}
tuple. The elements as implemented express an ordinal so it is possible to change this to allow duplicate tuples but I want to digest what the implications of that might be first, and to wait and see what if any pain it actually may cause in practice, before fixing something that may not be broke.
You could as stands move past this problem by moving to something like {"fire-repeat", "work", "3"}
.
We have enough here to feel confident in adapting to our needs in this area over time. We’re not walled in if we experience pain in this.
Reviewing our goals
TL;DR It went rather well, or I’d not be writing about it.
I listed a bunch of goal, principles or aspirations that are important to Inversion. I find it important after a non-trivial piece of work to consciously run down a mental check-list and ensure that I’m not negatively impacting any of those goals without compelling reason. The purpose of such a review is not to seek perfection but to ensure simple forward progress in each area, even if it’s only inching forward. Incremental improvement, kaizen and all that.
This is just my sharing informal observations after a piece of work. Normally I would use my internal dialogue.
Black-box components
Inversion has a strong opinion on behaviours as black boxes being as opaque as possible. This is why we don’t inject implementation components into behaviours, and encourage behaviours to use service location to locate the component they need for their implementation. The reasons for this are outlined in other pieces I’ve written and is something I’ll write about more in the future. The short version is a concern with leaking implementation details, and imposing externally visible has-a relationships upon components where uses-a would be more appropriate. A behaviour may use configuration to form the basis of component location, but that is a detail of implementation not common interface.
This concern speaks to behaviours only. How data-access components obtained from an IoC container are instantiated and injected for example is a separate and distinct architectural concern. Behaviours are participating in a component model around which there are specific expectations. Other services aren’t.
Anything that distracts from a behaviours condition and action is a potentially undesirable overhead, especially if its leaking details across the interface. Moving from multiple data-structures to the one whose interface does not express intent, focuses on being a simple, generalised, immutable structure of string values that can serve multiple purposes… While not a radical improvement in terms of behaviours as black-boxes, it’s a definite improvement. We’re leaking less. Configuration becomes a standardised input at instantiation.
Intent is expressed where desirable through the data-structures actual data, not it’s interface. This is what is meant by moving to a more generalised data-structure.
Substitution, “pluggability”
Related to the interest in black-boxes, this isn’t a Liskov concern. This is a very real and practical concern with being able to swap out components with alternate implementations. Change the type specified by that attribute and nothing else needs to change kind of swapping out. Behaviours as extensible plugins.
Again no tectonic shift here, as with the previous point, the focus of many interfaces into one common interface shared by many behaviours provides substantially less friction to behaviours as plugins.
Configuration as resource distinct from application
Expressing configuration is more standardised, expressive, and more elegant especially when using XML notation thanks to the Spring.NET extensions. The change is impactful enough that we’re now starting as a natural matter of course to express our own semantics through configuration.
So while configuration has not been made any more distinct as a resource, the quality of it’s use as a distinct resource has been much improved, and I have hope that it will over time become a pleasure to use and a welcome tool for the developer rather than an onerous liability.
Favouring composition over inheritance for application behaviour
With the original implementation there was a lot of pressure to inherit from various classes in order to inherit some of their configuration features. This caused a lot of strain on inheritance.
With the move to a common data-structure for configuration we took away pressure from inheriting to gain varying configuration properties.
With the move to predicates as methods extending
IConfiguredBehaviour
we took pressure away from having to inherit from a particular class in order to pick up it’s condition predicates.What we didn’t escape was the need to actually use these predicates in a condition, therefore making it desirable to inherit from some classes in order to obtain the checks they perform in their condition.
So this is really a 2 out of 3 in this regard. We have relieved pressure from inheritance in quite a marked way, but there remains an impediment that will require more thought and work.
Small footprint micro-framework
This was one of the primary reasons for the piece of work and one of the more substantial wins as it’s reduced down the footprint of the behaviour interface and provides a strategy for accommodating future change without modification. Behaviour configuration is in a markedly better state than it was. Far more compact in design.
Single responsibility
Providing configuration features was starting to distract from a behaviours responsibility to provide a condition/action pair, with an emphasis on the action. Most of the responsibility for expressing configuration and working with it has been removed from the behaviour which for the most part now merely has a configuration that was provisioned by a base class and is acted on by extension methods. So our focus on the actual responsibility of behaviours has been tightened.
Extensibility, DIY
This again was one of the primary reasons for performing this piece of work. There was a desire in the face of feature requests concerning configuration and predicates to be able to reasonably reply “do it yourself”.
On the one hand there’s a big gain. RDF is able to describe the world with triples, and it turns out N-Quads is a thing. The point is, in terms of data expression you can drive a Mongolian Horde though an ordered set of four element tuples. It makes it very easy for other developers to extend with their own configuration expressions.
As mentioned previously adding new predicates as extension methods is now also smooth.
We’re still stuck on having to actually use these predicates as mentioned.
The issue isn’t implementing the lookup of predicate strategies, it can be as simple as a dictionary of lambdas, the cause for concern is where to define this, and where to inject it. Which object should be responsible for maintaining this lookup? It probably fits well enough on the context, but it would require the context to hold implementation details of behaviours, and I want to think about that some.
Inversion of Control
I’m not sure I would go so far as to say IoC has been significantly enhanced here. Behaviour implementations have certainly relinquished much of their control over their configuration. Perhaps a nudge in the right direction for IoC is that it is now easier for developers to drive both their condition and action from configuration, so we have perhaps afforded more opportunity for IoC.
Testability
No big wins in functional terms here, the more concise and expressive configuration is simply easier and more pleasant to use, so unit tests for example that would tend to want to configure a wide variety of cases and so are big users of configuration certainly benefit.
While I was rummaging around the framework touching lots of different bits I also took a slight detour to implement MockWebContext along with MockWebRequest and MockWebResponse as I had a need to lay down some half-decent tests. Nothing exciting, you can see their use in ViewPipelineTests.
So overall this patch of work puts Inversion in quite a strong position for testing with it possible to test contexts running a full application life-cycle for a request, or any behaviour or group of behaviours in concert as needed. Very few behaviours have a dependency on
IWebContext
, in this case only those parsing the request and writing the response, so testing even a view pipeline is straight-forward.Portability
No big impact here except there’s less to port. The use of LINQ statements is an implementation detail, and there are easy equivalent implementations available on all common platforms. There’s nothing exotic being done here.
Conventions for state
Inversion attempts to place mutable state in known places, and to keep state elsewhere as immutable and simple as possible. We’ve consolidated down our configuration to a single immutable structure, so a small nudge in the right direction.
Speed
Performance tests are showing the same figures. There wasn’t expected to be any change here, a move to backing configuration with arrays in the future may squeeze out some performance gains.
Other observations
I’m starting to become mildly concerned over the use of LINQ methods used in a fluent style in implementation code. I have become aware of how often when debugging I am changing a linq statement into a simpler form in order to step through it and see what’s happening. I take this as a very loud warning sign. Often my use of linq is pure vanity as it has go-faster stripes. I think I’m going to start avoiding fluent chained statements, and expose the intermediary steps as local variables in order to make debugging easier… Difficult to force myself perhaps, as linq is bloody expressive.
Future work
TL;DR My flaky ideas.
There’s a couple of progressions I can see to this work, but first I want to let the existing work bed in before jumping the gun.
Backing the configuration elements with an array
At the moment the Configuration
is backed by ImmutableHashSet<IConfiguratonElement>
. This is reasonably efficient, and is easy to work with. It could however be moved to being backed by an array:-
|
|
Which would probably be more efficient.
I did it this way as it was easier to reason about and debug, and those are still valid reasons at the moment. Once it’s become part of the furniture, then I can think about trying this out.
Expressing tuples relative to the preceding tuple
There’s an improvement I can vaguely see… because the tuples are ordered, we can consider a new tuple as an expression relative to the previous tuple.
|
|
Relations of tuples include a lot of repetition in many cases. Using an expression of an offset from the end would allow us to express an uncapped arity of tuples with the limit being on how many new elements of a tuple we could expand by at a time. They could get effectively as large as you like… Think of scanning the list of tuple definitions using a stack as a point of context, you pop the specified amount of elements, and then push the rest on, the result is your current tuple. You could put this stack based iteration behind IEnumerable<IConfigurationElement>
and anybody using it say via LINQ would be none the wiser.
My thinking on this is still fuzzy, and I feel it may be more than is required, possibly turning something quite straight-forward into something quite convoluted. Once I’ve thought through it a bit more, it may just be an obviously bad idea in practice.
Also sometimes a little constraint is an appropriate restraint. Time will tell.
The lookup of condition predicates
As discussed, at the moment predicates to act on configuration are provided as extension methods which need to be used in conditions. The frame
of a tuple could be used as a key to lookup the predicate to apply to it by a variety of mechanisms.
This would add extensibility but may be one indirection too far.
In parting
I always feel a bit odd after I write something like this up. I’m not sure what use or interest this is beyond a small group of involved people, but I find I’m getting a lot of value out of explaining a thing in a public context. It’s certainly encouraging my thinking toward more rigour, so it’s a worthwhile activity for that reason alone.
My attempt at writing this up isn’t to show any arrival upon a perfect landing spot, but instead relate in some way software development and architectural concern as an incremental process of improvement moving toward a goal.