Thursday, January 25, 2007

Architecture Refactoring

Refactoring as introduced by Martin Fowler is an excellent practice for bottom-up and structure-preserving gardening activities in your implementation. Refactorings are documented in a canonical form that resembles pattern templates to some extent. A typical example is Extract Interface. Assume, you've designed otherwise unrelated classes, each of them implementing the methods store() and load() for serialization. To treat these classes polymorpically, extract the common methods to a common interface, for instance IStorable that provides these methods and is implemented by the aforementioned classes. In an agile setting, refactoring denotes an essential tool, because the incremental integration of new use-cases keeps the tactical architecture aspects in flux which means that implementation artifacts require continuous modifications. You should "embrace change" as Kent Beck once said instead of trying to circumvent it - which, as we know, is impossible.

As you might remember, Joshua Kerievsky came up with "Refactoring To Patterns" in 2004. The basic idea behind his excellent book is to identify places in an implementation that use proprietary solutions for problems which should be better solved using patterns. I also want to give you an example for this approach. Suppose, you have implemented a class ShareValues that contains a list of consumers. Whenever a share value changes, you iterate through the list and call event handler methods on each of these objects. While simple to implement, the flip side of this approach is the tight coupling unnecessarily introduced. As soon as new consumers are added, we have to manually modify the ShareValues's list of consumers. Obviously, applying the Observer pattern comes to our rescue here. The advantage of refactoring to patterns is that we are able to introduce a higher abstraction level.

If you think that even further, we enter the world of what I call Architecture Refactoring. In Architecture Refactoring we refactor the architecture itself.

Possible Examples:
  • Partition Responsibilities: If a component or subsystem got too many responsibilities, partition the component or subsystem into multiple parts, each of which with semantically related functionality.
  • Extract Service: If a subsystem does not provide any interfaces to its environment but is subject of external integration, extract service interface.
  • Introduce decoupling layer: If components directly depend on system details, introduce decoupling layer(s).
  • Rename Entity: If entities got unintuitive names, introduce appropriate naming scheme.
  • Break Cycle: When encountering a cycle on subsystem level, break it.
  • Merge functionality: If there is broad coheshion between two modules, merge them.
  • Orthogonalize: If two parts of an architecture introduce different solutions for the same problem, choose one preferred solution and eliminate the other.
  • Introduce strict layering: If in a layered system, a layer accesses lower layers without necessity (relaxed layering), enforce strict layering.
  • Introduce hierarchies: If several entities are only variants of a particular entity, introduce a hierarchy.
  • Introduce Interceptor hooks: If we have to open an architecture for out-of-band functionality according to the Open/Close principle interceptors should be introduced.
  • Eliminate dependencies by dependency injection: Reduce direct and wide-spread dependencies of Parts in a Whole/Part setting by introducing a central runtime component (Whole´) that centralizes dependency handling with dependency injection.

So far, these are only suggestions of potential candidates. But you can see the possibilites now. Obviously, we should collect a whole catalog of such architecture refactorings. Note, that higher-level refactorings might incur lower-level refactorings the same way architecture patterns often refer to design patterns. Extract Service will very likely lead to an Extract Interface refactoring if the implementation already exists. Orthogonalize is heavily leveraging refactoring to patterns.

From my viewpoint, we have just started to understand and apply refactorings in a more holistic context. As the old saying goes, we have just seen the tip of the iceberg. Architecture Refactorings will guide architects to identify potential problems in a software architecture and also provide them with refactorings to solve those issues.

10 comments:

Stefan Tilkov said...

You don't know how great it is to see you using actual paragraphs.

Oh, and the post is great, too :-)

Michael said...

Thanks a lot.
I guess, I don't use paragraphs too often to save white space. Just kidding :-)

Anonymous said...

This is interesting, but I see a major problem in how you would describe a refactoring on the architectural level.

On the code / design level, refactorings can be automated (which is one of the major attractors here).

I haven't read the book, but I suppose that on the pattern level, it is possible to describe a refactoring because patterns are pretty well understood and there is a more or less agreed upon way to describe a pattern.

On the architectural level, it is not quite clear to me how to describe refactorings in a clear and concise way. The problem here is that there is no agreed upon way to describe an architecture, and thus it is hard to describe a refactoring as a series of transformations to an architectural model. This of course also means that tool support is out of the question.

On the other hand, I don't know enough on the subject, so I might as well be talking out of my ass =).

Michael said...

If there is a way to describe an architecture there is a way to describe an architecture refactoring. I am mostly documenting architecture refactorings using pattern-like architecture descriptions, for example class/object diagrams, state diagrams, etc. Note: most of us already have done architecture refactoring, because we were forced to by changes or faults. However, we applied an unsystematic ad-hoc approach. Why not describe general architecture refactorings as patterns? That's exactly what I've doen and it works pretty well. It is a little bit like the work done in reengineering patterns by Oskar Nierstrasz et al.

Anonymous said...

Of course there is a way to describe a refactoring if there is a way to describe an architecture. That's exactly what I meant, but the problem is that there is no real standard (like UML for design structures) to describe structures on the architectural level (at least not as far as I know of).

I know there are various attempts, but none can be viewed as any kind of de-facto standard.

Sure, most have done ad-hoc architecture refactorings - this is a must when maintaining a system and adapting it to for example changing nonfunctional requirements.

But as I said, I believe that refactorings are only of any value if described in a way that allows others to easily apply them systematically as a series of transitions of some description of their own architecture.

This of course only works if there is a common way to describe architecture.

What do you mean by describing architecture refactorings as patterns? Do you have any example for this handy? I'm interested to see how that would work.

Michael said...

Dear Kristian.

as said before. If there were no way to describe an architecture, there would be no way to describe a refactoring. Architecture refactorings are patterns like code refactorings. They define the semantic-preserving transformation of an architecture. Let me introduce two examples.
Suppose, someone has implemented a layered system (=> Layers Pattern) and used relaxed layering instead of strict layering. The appropriate architecture refactoring defines how to transform from the relaxed to the strict version. There are various variants depending on the kind of functionality available in the layers. Of course, this is a reversible refactoring that could be also done the other way round: you got a strict layering but for performance reasons need to relax the layering. The refactoring pattern is described exactly at the same level as the Layeres pattern.
Another refactoring is dealing with breaking dependency cycles between subsystems.
In the tutorial you will see moch more details of such refactorings. For example, their context, problems (and forces), solution, "implementation issues", variants, relations to other refactorings.

Anonymous said...

Ok, I guess that I was missing the point there. So if I understand this correctly now, it doesn't really matter in which way you describe your architecture, because ultimately, it's the same thing, no matter whether it's described by a UML diagram in some software, some drawing on a napkin or just prose. A layered system is a layered system, so you can apply a refactoring to it.

Thanks for your patience in explaining this to me.

Unknown said...

I tend to agree with Michael that an architecture can be refactored even if it has not been defined in some formal way. Although this might prevent you from creating tools that can apply the refactorings automatically, they are still just as valid and useful.

In another post, I stated that the architecture of a system is defined by its implementation. I believe this is true for a system that has been implemented. Once implemented, the architecture documentation and diagrams that drove the implementation become stale and (possibly) inaccurate over time.

However, I now see that Michael means to apply these refactorings even before implementation. Can we know that a system's architecture needs refactoring before we have experienced the pain of its implementation? I think, yes, perhaps...

Sebastian Dietrich said...

Any news on literature on architecture refactoring?

AeroGMan said...

I agree with the concept that you can refactor the architecture even if there is not an associated architecture description; however, what is necessary is an established architecture value framework that expressly discerns good outcome of refactoring vs less good outcome of refactoring at the architecture level. So what is that value framework, and if by chance we have a relatively faithful reflection of the architecture description (as if its one thing, which it isn't), no small tasks, then we could evaluate based on the value framework goodness of future changes and choose a direction. That being said, this is a wicked problem. Architecture refactoring can make single-stage thinking errors that could produce a good apparent initial outcome that has deeper poor implications if we don't factor in the business decisions that established the existing architecture and factor that directly into our value framework. I am with Sebastian, if there are any architecture resources please share them. Some of the things that I have extracted nuggets from are writings around continuous architecture and architecture validation, architecture evaluation. I have also looking at change histories evolutions of architectures and rationale to look at evolution pattern outcomes of business drivers and architecture decision. Simultaneously exploring scenarios in this context used for futureproofing the architecture. But its like assembling franken-ideas into something cohesive, mentally taxing. Ideally, someone else has already assembled these concepts and I can just use them. So far, nothing as clear as for implementation.