Friday, August 28, 2009

About Modularity

Despite common belief, splitting up an application into a number of bundles is not automatically introducing modularity. The almost magic benefits of modularity are caused by the particular decomposition. Many decompositions can actually increase complexity, only the right decompositions reduce complexity. This was perfectly demonstrated by the father of modularity David Parnas. In his seminal paper On the Criteria To Be Used in Decomposing Systems into Modules (PDF), written in 1972. Parnas takes a simple problem, a text indexer, and shows two different decompositions. He then added the requirement that the source text files could be very large, no longer fitting in memory. The first decomposition required changing each module, the second decomposition only had one affected module.

Parnas' paper clearly shows why creating the correct decomposition is so hard, it is about predicting the future. With this disclaimer, there are some general rules that apply. These rules are in their essence the same as we use daily in writing our object oriented code. The most important being information hiding. Information hiding works because if you do not know something, you cannot make false assumptions about it. Assumptions that could break your code when violated in runtime.

With modularity, we have the fantastic tool that the rules inside the module are different from the rules outside the module. As a module author we can control what we expose from the inside and what is hidden from the outside. We can make sure that outsiders cannot make assumptions about our internal workings because they do not have access to them. By minimizing our dependencies on other modules we can reduce our own assumptions of the outside world. The less we assume, the more resilient our module will be to changes in the outside world.

In OSGi we even quite far with this model. In the initial model, you could basically only export API, not implementation classes. Each bundle could only talk to other bundles through services. A bundle is not even allowed to make assumptions about its and other modules' life-cycle. Though for many this sounds extreme, it was put in place to minimize assumptions that are so easily broken.

But there is more to modularity than just robustness and resilience. The proper decomposition can also simplify by requiring much less code. In 1984 I was the designer for an object oriented window system used in a page makeup terminal, this was my largest object oriented design so far. This design process was accompanied with an uneasy feeling that I was not designing, I was only postponing. Every abstraction seems to just postpone the really hard problems. However, one day I realized that I was done without ever having felt that I solved those really hard problems, they seem to have disappeared. In small this effect is visible with recursive algorithms. With the right decomposition, you need very little code, with the wrong decomposition you wrestle with the special cases of start and ending. The right decomposition somehow removes the need for much code.

I do not have a good understanding where the complexity goes when you have a proper modular structure but I have seen it too many times to know it happens. The problem is that once you found a proper decomposition, it feels so natural that you can't understand why you did not see this immediately.

Modularity provides benefits when it is used in the proper way, splitting an application into parts is not guaranteed to give you these benefits. If the cohesion is low and the coupling high between modules it is likely to give you more pain than gain. But there are more ways you can kill the benefits. If you break through abstractions, you likely will be broken when the implementation behind those abstractions change. Unfortunately, there are patterns in Java, mainly class loading related, that are fundamentally not modular and can easily kill the benefits of modularity.

However, if you refactor that application and you hit that right composition of modules, I can ensure you that you will immediately know why it is worth it.

Peter Kriens

6 comments:

  1. I always wondered if other designers had that experience with the designing process as well, but now I know. Especially the struggling and finding the natural solution. Very recognizable.

    ReplyDelete
  2. It would be very nice to have some 'modulularity' based FindBug type of tools and/or refactoring tools for Eclipse. It is so easy to be lazy and use require-bundle or to expose too much in packages. Is there any working being done in this area?

    ReplyDelete
  3. Peter, what you have described closely relates to the "tyranny of the dominant decomposition": In traditional object-oriented or component-based systems, you typically have only one way of decomposing your software into smaller parts, like classes or modules. There will always be concerns that do not align with that modularization and end up scattered across multiple modules and tangled with other concerns. That's what's usually addressed with the aspect-oriented paradigm.

    Now if you're an exceptionally good architect (or if you're just lucky), your choice of (traditional, dominant) decomposition can be a good one and the impact of such cross-cutting concerns can be minimal. To some extent, you can also avoid such problems by providing facilities for extensibility at certain points in your software system. That's what distringuished a well-designed and modular system from an average one.

    ReplyDelete
  4. I think that Bundle events pretty much blow this argument out of the water, Peter. The extender pattern is nothing but a bundle which makes a sh*tload of assumptions about another bundle's life cycle - controls them, too.

    Reflection is a part of every system, including OSGi. Reflection is a rather interesting violation of your modularity principles, no?

    ReplyDelete
  5. John:
    I do not thinks there are such tools, though I would be very interested in them.

    Hal:
    No :-) Though we agree that you loose the module magic when an extender (or reflector) does its work without being under control of the bundle because the differences between the inside and outside become fuzzy. For example, like indiscriminate classpath scanning.

    That is the reason that all OSGi specs that use the extender pattern have rules that keep the bundle in charge of what resources can be used and under what contract. In this way, an extender is a tool under control of the bundle, even if that tool controls the life cycle of that bundle. For example, in Declarative Services this is the Service-Component header. It is the bundle that defines that it uses DS and where the resources are stored, DS is just a helper.

    However, this blog was not about specific mechanisms of how to achieve modularity, it was about that fact that using a technique (in this case decomposing into bundles) does not automatically provide benefits. Using the security API does not guarantee a secure application, nor does bundleizing intrinsically gives you the modular magic. You still have to get the decomposition right.

    Peter Kriens

    ReplyDelete
  6. Hi peter and thanks for every blog that you share with us.Much appreciated.

    ReplyDelete