Skip to content

Evolving Architectures – Part VI – 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 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 define some minimal architecture at an early stage. Yet, at the same time, I envisioned enough of the direction it would take and its future extensions to allow it to grow. Note the idea is not to design future extensions. 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 foresee, however, that it would later have to support Sagas; I did foresee, it would have to support partial failures;  I assumed it would have to handle allocation limits, etc. I did not design any of these extensions at the onset. As time passed, we needed additional functionality, some of which required complex design. 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 much 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 added and how much more design was needed from the initial version (which only had the first method, RaiseEvent)

public interface ImEventBroker
{
  ///
  /// Raise a one-time event, next event can go to diffent resources
  /// 
  /// 
  ///
  /// 
  bool RaiseEvent(TEvent eventDetails) where TEvent : ImEvent;
 
  ///
  ///  Raise an event within a long running interaction
  /// 
  /// 
  ///
  ///
  /// 
  bool RaiseSagaEvent(Guid sagaId, TEvent eventDetails) where TEvent : ImEvent; ///
 
  ///
  ///  Raise an event within a long running interaction
  /// 
  /// 
  ///
  /// 
  bool RaiseSagaEvent(TEvent eventDetails) where TEvent : ImSagaEvent;
 
  ///
  ///  Raise an event within a long running interaction
  /// 
  /// 
  ///
  ///Don't send this event if it took more than maxRelevanceTime miliseconds to handle it
  /// 
  bool RaiseSagaEvent( TEvent eventDetails, int maxRelevanceTime) where TEvent : ImSagaEvent;
 
  ///  Raise an event within a long running interaction
  /// 
  /// 
  ///
  ///
  ///Don't send this event if it took more than maxRelevanceTime miliseconds to handle it
  /// 
  bool RaiseSagaEvent(Guid sagaId, TEvent eventDetails,int maxRelevanceTime) where TEvent : ImEvent;
 
  ///
  ///  Raise an event that starts a long running interaction
  /// 
  /// 
  ///
  ///
  /// 
  bool BeginNewSagaEvent(Guid sagaId, TEvent eventDetails) where TEvent : ImEvent;
 
  ///
  ///  Raise an event that starts a long running interaction with a specific routing
  /// 
  /// 
  ///
  ///
  ///the name of the route to use
  /// 
  bool BeginNewSagaEvent(Guid sagaId, TEvent eventDetails,String routing) where TEvent : ImEvent;
 
  ///
  ///  Raise an event that ends a long running interaction
  /// 
  /// 
  ///
  ///
  /// 
  bool EndSagaEvent(Guid sagaId, TEvent eventDetails) where TEvent : ImEvent;
 
  ///
  /// Remove a resource from the saga - doesn't close the saga itself. Only use in an orderly shutdown
  /// 
  ///
  /// 
  bool DetachSaga(Guid sagaId);
 
  ///
  /// close a saga asynchronously (only works if you started the saga), with a callback for the result
  /// 
  ///
  ///
  /// 
  void CloseSagaAsync(Guid sagaId, Action onComplete);
 
  ///
  /// close a saga asynchronously (only works if you started the saga). fire and forget
  /// 
  ///
  void CloseSagaAsync(Guid sagaId);
 
  ///
  /// Close a saga whether you started it or not
  /// 
  ///
  /// 
  bool ForceCloseSaga(Guid sagaId);
 
  ///
  /// Allows you to drop pending events (which are queued for sending)
  /// Warning : There is no guarantee as to which events will be sent/deleted
  /// 
  ///
  /// 
  bool ClearPendingEvents(Guid closedSagaId);
 
  event SagaEvent SagaClosed;
  event SagaEvent SagaClosing;
  event SagaEvent SagaInitiationFailure;
 
}

Another example of 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 undergone many changes over the years. Modern wiki platforms allow for formatted text, permissions, editors, multimedia, etc. The basic design, architecture, and direction still hold and can accommodate 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 a 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 illustrate the difference. Simplification vs. Up front designOn the right side, we see an illustration for Simplification. We designed something to fit the requirements as they are needed today. If that is 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 have already designed, to some extent, the complete solution in 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 features we may not need eventually, whereas, with Simplification, we add designs as needed.  If we look back at the EventBroker example I mentioned earlier. There were a few features I thought 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 the up-front design, I would have added several infrastructure bits to enable it when the need arose – i.e., three 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 its place. As mentioned, the tricky part is developing 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.

 


 

Published inAgile ArchitectureBlogFeatured Posts

One Comment

Comments are closed.