[Edited: added explanation of the difference between upfront design with iterative implementation and Simplification]
The previous installment in the series talked about leaps as a technique to change the architecture. Leaps are more of a revolution than an evolution and even though they are sometimes needed evolving an architecture is the more interesting topic. This and the next post deal with the two types of evolution strategies Simplification and Parallel. In Part IV I provided the following definition for Simplification (where the base definition is Kent Beck’s and the projection on software architecture is mine):
Simplification – is when you don’t know what you want to do and it is too expansive to directly get to an end result. This is the basis of the iterative system. start small and gradually add features as you go. From an architecture perspective it is challenging to to get to a core simplified architecture you can grow later. This is useful as long as the changes in the architecture are within the expected growth path. when it is time to make a change in the architecture you’d need to go with the other strategies.
Simplification essentially means we’ve “somehow” managed to to define some minimal architecture at an early stage. Yet, at the same time envisioned enough of the direction it will take and its future extensions to allow it to grow. Note the idea is not to design the future extension they’ll be designed when their time comes. When it works this is really cool since you can start small and expand as you go, and since you “know” in advance where you’d be going the parts fit together well.
In xsights, for example, I designed an eventing infrastructure . When I started it, it only supported simple events. I did foresaw however, it would later have to support Sagas; I did foresaw, it would had to support partial failures; I assumed it would have to handle allocation limits etc. I did not design any of these extensions on the onset. As time passed by we needed the additional functionality and some of it required complex design (e.g. see for example adding Rendezvous thread synchronization when I worked on allocation limits). However since all these additions were anticipated the base design was able to accommodate them – sometimes the extension point was pre-built and sometimes not, but in all these cases it was pretty known where the new parts would fit in. – the base architecture was “distributed events” (implemented over RPC – WCF HTTP channels) which was extended with Sagas and Reservations at the architectural level and a lot of refactoring and design to make everything work together – still the base premise of taking business, automatically translating them into network calls, hiding the complexity of handling communication (and communication errors) prevailed.
You can take a look at the EventBroker interface below to get a feel for how much was was added and how much more design was needed from the initial version (which only had the first method RaiseEvent)
public interface ImEventBroker { /// <summary> /// Raise a one-time event, next event can go to diffent resources /// </summary> /// <typeparam name="TEvent"></typeparam> /// <param name="eventDetails"></param> /// <returns></returns> bool RaiseEvent<TEvent>(TEvent eventDetails) where TEvent : ImEvent; /// <summary> /// Raise an event within a long running interaction /// </summary> /// <typeparam name="TEvent"></typeparam> /// <param name="sagaId"></param> /// <param name="eventDetails"></param> /// <returns></returns> bool RaiseSagaEvent<TEvent>(Guid sagaId, TEvent eventDetails) where TEvent : ImEvent; /// <summary> /// <summary> /// Raise an event within a long running interaction /// </summary> /// <typeparam name="TEvent"></typeparam> /// <param name="eventDetails"></param> /// <returns></returns> bool RaiseSagaEvent<TEvent>(TEvent eventDetails) where TEvent : ImSagaEvent; /// <summary> /// Raise an event within a long running interaction /// </summary> /// <typeparam name="TEvent"></typeparam> /// <param name="eventDetails"></param> /// <param name="maxRelevanceTime">Don't send this event if it took more than maxRelevanceTime miliseconds to handle it</param> /// <returns></returns> bool RaiseSagaEvent<TEvent>( TEvent eventDetails, int maxRelevanceTime) where TEvent : ImSagaEvent; /// Raise an event within a long running interaction /// </summary> /// <typeparam name="TEvent"></typeparam> /// <param name="sagaId"></param> /// <param name="eventDetails"></param> /// <param name="maxRelevanceTime">Don't send this event if it took more than maxRelevanceTime miliseconds to handle it</param> /// <returns></returns> bool RaiseSagaEvent<TEvent>(Guid sagaId, TEvent eventDetails,int maxRelevanceTime) where TEvent : ImEvent; /// <summary> /// Raise an event that starts a long running interaction /// </summary> /// <typeparam name="TEvent"></typeparam> /// <param name="sagaId"></param> /// <param name="eventDetails"></param> /// <returns></returns> bool BeginNewSagaEvent<TEvent>(Guid sagaId, TEvent eventDetails) where TEvent : ImEvent; /// <summary> /// Raise an event that starts a long running interaction with a specific routing /// </summary> /// <typeparam name="TEvent"></typeparam> /// <param name="sagaId"></param> /// <param name="eventDetails"></param> /// <param name="routing">the name of the route to use</param> /// <returns></returns> bool BeginNewSagaEvent<TEvent>(Guid sagaId, TEvent eventDetails,String routing) where TEvent : ImEvent; /// <summary> /// Raise an event that ends a long running interaction /// </summary> /// <typeparam name="TEvent"></typeparam> /// <param name="sagaId"></param> /// <param name="eventDetails"></param> /// <returns></returns> bool EndSagaEvent<TEvent>(Guid sagaId, TEvent eventDetails) where TEvent : ImEvent; /// <summary> /// Remove a resource from the saga - doesn't close the saga itself. Only use in an orderly shutdown /// </summary> /// <param name="sagaId"></param> /// <returns></returns> bool DetachSaga(Guid sagaId); /// <summary> /// close a saga asynchronously (only works if you started the saga), with a callback for the result /// </summary> /// <param name="sagaId"></param> /// <param name="onComplete"></param> /// <returns></returns> void CloseSagaAsync(Guid sagaId, Action<bool> onComplete); /// <summary> /// close a saga asynchronously (only works if you started the saga). fire and forget /// </summary> /// <param name="sagaId"></param> void CloseSagaAsync(Guid sagaId); /// <summary> /// Close a saga whether you started it or not /// </summary> /// <param name="sagaId"></param> /// <returns></returns> bool ForceCloseSaga(Guid sagaId); /// <summary> /// Allows you to drop pending events (which are queued for sending) /// Warning : There is no guarantee as to which events will be sent/deleted /// </summary> /// <param name="closedSagaId"></param> /// <returns></returns> bool ClearPendingEvents(Guid closedSagaId); event SagaEvent SagaClosed; event SagaEvent SagaClosing; event SagaEvent SagaInitiationFailure; }
Another example for Simplification, given by Kent Beck, is the idea of the Wiki by Ward Cunningham. The design of the Wiki as a way to allow collaborative editing has gone through a lot of changes over the years. Modern wiki platforms allow for formatted text, permissions, editors , multimedia etc. the basic design, architecture and direction still holds and can accomodate the new features and evolution.
So how do you get to Simplification – ah that’s easy :) All you need is a stroke of genius, as in the case of Ward Cunningham or deep understanding of the problem domain, like I had in the case of the EventBroker – prior to designing and building it I’ve worked on quite a few distributed systems where I designed and evaluated communications infrastructures. Having done that provided enough perspective to understand how I can start small and grow the design without invalidating the initial design.
One important distinction to make is the difference between Simplification and up-front design which is implemented iteratively. The Figure below tries to illustrates the difference. On the right side we see an illustration for Simplification. We designed something to fit the requirements as they are needed today. If that was done right, the design will hold through and future requirements will fit into the design and extend it (if not the Simplification was wrong and then we either Leap or Parallel to move to a new design). On the left side we see an illustration of an Up-front design. In this case we already designed to some level the complete solution in the light of future requirements that we have in mind. Moving forward to implementation, we can implement the design in slices to get faster feedback etc. The up-front design is more wasteful as we invest time designing feature we may not need eventually where-as with Simplification we add designs as needed. If we look back at the EventBroker example I mentioned earlier. There were a few things I features I thought that we may need in the future that were never implemented – the actual need didn’t come up for instance the ability to defer handling of messages (e.g. posting to a queue). I can think of several ways to add this to the EventBroker design but there’s nothing supporting that in the current one. With up-front design I would have added several infrastructure bits to enable it when the need arise – i.e. 3 years of surplus code that needs testing and maintenance
Simplification is a great way to go about evolving an architecture as the evolution is intuitive you just add the new building blocks as you need them and each new design block has it place. The tricky part, as already mentioned, is coming up with a Simplification that can hold. We need an additional design strategy, one that will allow us to make unexpected changes like Leap does, but will allow us to evolve the architecture like Simplification does – That strategy is called Parallel and we’ll discuss it in the next part
Struggling with balancing agile and architecture ? let me help
Illustration by Josh Schwartzman
[…] This post was mentioned on Twitter by Arnon Rotem-Gal-Oz, Jarrod Marshall. Jarrod Marshall said: RT @arnonrgo: Blogged: Evolving Architectures part VI – Simplification http://bit.ly/cE3XRv #Agile #SoftwareArchitecture […]