Fractal Design Patterns

The difference between Architecture and Code blurs quickly when refactoring becomes sufficiently common, so the distinction made between various pattern languages never seemed especially helpful to me. Between Architecture and Service the line is firmer: this code is mine, that code is yours, here is the interface. At the same time, I've found that the design patterns that work when I'm writing methods and classes still apply when I'm working with services. The goal is still to increase cohesion and decrease coupling, even if often I have no control over half of the code.

Thus, the idea of a Fractal Design Patterns. Instead of the usual pattern description, which describes the pattern at a specific level of abstraction, a Fractal Pattern would illustrate it at multiple levels and try to get at the underlying principle.

For example, I'll take the algorithm-swapping-base-on-state that is described by the Strategy pattern.

At a code level might look like a conditional statement that isn't worth encapsulating yet:

Bottle wine = hasPreferredRegion() ? selectFromRegion() : findPrettiestLabel();

 

At an architecture level it might look like the classic GOF pattern:

public interface iChooseWineStrategy {

public Bottle choose(Cellar cellar);

}

public class ChooseRegionalWine implements iChooseWineStrategy {

public Bottle choose(Cellar cellar) {

return cellar.selectWineFrom(cellar.getPreferredRegion());

};

}

public class ChoosePrettyLabel implements iChooseWineStrategy {

public Bottle choose(Cellar cellar) {

return cellar.getPrettiestLabel();

}

}

 

And in an http-based RESTful-ish API, it might just look like different resources:

http://dinner.com/wine/region/preferredregion/1

http://dinner.com/wine/prettiestlabel/

 

To include the Strategy decision in the API, instead of isolating the decision in the client, involves using hypertext-as-engine-of-state.  For example, the client might start with a call like:

http://dinner.com/diner/dinerid/

with a response that would include something like:

<link href="http://dinner.com/wine/region/preferredregion/1" rel="http://schemas.dinner.com/wine/region" title="winepreference"/>

And proceed from there.  Both are examples of the Strategy approach, though only the link version involves extra flexibility over the GOF pattern.

 

All three of these approaches solve the same problem of how to select an algorithm at run time based on state. They all use the same idea of indirection to some algorithm-executer based on data held by the environment, whether it is predefined at compile time, the currently-held strategy or a resource resolved by DNS.

 

Each level of abstraction provides more flexibility. In the first case, the decision is located in one location and the number of possible strategies are limited. On the other hand, it only takes looking in one place in one file to know what might possibly be happening.

In the second case, the decision may be made anywhere in the code and the number of possible strategies are unlimited, but they must be specified at compile time (metaprogramming is not included in the Strategy pattern). It requires looking at all implementations of the interface and anywhere in the code the strategy might be set in order to understand the actual behavior of the system.

In third case, the decision may be made on the client, on the server or somewhere in between, and if it fails it may have been a blip caused by the unreliability of the network or a failure of the service as well as something explicitly performed in either set of code. On the other hand, if multiple resources implement the same API we can swap between them at run time, in different geographic locations and potentially run by multiple companies. Not only are the strategies unlimited, they are no longer limited to strategies that were enumerated at compile time, or strategies we implemented at all. On the other hand, it is impossible to determine the possible behavior of the system by reading the client-side code.

 

I find such parallels most useful when moving from one level of abstraction to another. If I come across the first case when I need to add a third approach I can immediately suspect that the full Strategy pattern will be useful. When working with a Service, when I see multiple resources to get at information in different ways, I can treat them like I would strategies or encapsulate them as such. If a particular application of the Strategy pattern has become unwieldy or counterproductive I can encapsulate, constrain or inline to bring a decision closer to a code-level conditional.

 

Not every pattern is fractal. Implementation patterns regarding indentation or iterators obviously don't apply to method signatures. Likewise, stateful patterns like Observer don't fit well into a REST-ful paradigm. Nevertheless I think the description of vertical categories of complexity-handling strategies can make the transitions between levels of abstraction less jarring.