ARM yourself for Enterprise Application Development

This article was originally published in VSJ, which is now part of Developer Fusion.

If you’ve been involved in large scale software development for any length of time, you’ll surely recognise the following symptoms of application architecture and design decay:

  • you keep having to cut’n’paste repetative code (along, of course, with any associated bugs), as proper re-use of code isn’t a viable option. Poor code factoring makes this impossible;
  • this in turn leads to duplication existing all over the place – making a functional single simple functional change then requires amendments to be made all over the place;
  • the choatic package structure of the application means there is a prohibitively expensive learning curve for new staff – there’s no clarity of responsibility in what package does what;
  • everyone keeps treading on everyone else’s toes when making changes to the system – lack of any consistent packaging rules make intelligent work scheduling difficult;
  • automated testing is difficult, it’s impossible to test any package in isolation due to poor dependency management;
  • bug fixing is difficult, spaghetti-like class dependencies make it difficult to track the source of an error,
  • all in all, the whole application seems to be unstable, simple functional changes require large code rewords, and everything breaks all the time;

In this article I discuss an architectural reference model (ARM) for large scale applications, that I’ve successfully employed on three large enterprise application developments in the last few years (two in the finance sector, and one in on-line auctioning).

The ARM is made up of of five architectural stata (Interface, Application, Domain, Infrastructure and Platform). The overriding purpose of the ARM is to provide a clear set of rules for large scale application decomposition, that encourage seperation of concerns, maximises re-use of code (primarily) within the application and (secondarily) across applications, that leads to good code factoring without duplication, and increases application stability in the face of changing requirements.

To achieve this end, the ARM has an associated, and fairly easy to apply, set of rules which force a coherency of structure on your application, leading to: greater clarity of responsibility – as to which code does what, improved code factoring – reducing duplication within the code, increased consistency in packaging rules, more manageable dependencies between packages, thus mitigating to some degree the problems outlined above.

1. Introducing the ARM

figure1

Figure 1 – The five strata of the architectural reference model – each of which acts as a placeholder for one or more packages. The stratum in which a package “lives” is determined by what the classes within the package do, and what other packages the classes depend on.

1.1. Application sub-division

The ARM – see figure 1 - is based on five fundamental architectural strata – each of which I’ll describe in some detail in the coming sections. Each stratum acts as a placeholder for the packages that, combined together, will make up the source code to your application. It’s important to note that the stratum are not themselves packages, but – as per the rules in the following section – help to determine what should and shouldn’t be in any individual package.

The ARM has two sets of associated rules: general rules – which apply across the whole model, and stratum specific rules, which apply to each stratum individually:

1.2. The rules (general)

  • strata are divided according to functionality and ‘natural’ dependencies.
    Each stratum has an associated set of responsibility guidelines (a.k.a. concerns) which determine what the packages and classes within it are allowed to do. Each stratum is ‘naturally’ dependent on strata below it – in the sense that the functionality of the classes within it will be built using the facilities provided by lower stratum classes and packages.
  • depend downwards.
    Packages in a given stratum are only allowed to depend on - import from - packages in the same or lower stratum. Dependencies to packages in the same stratum should be avoided where this does not unduly increase complexity.
  • packages can only live in one stratum.
    A package that contains some classes that belong in one stratum, and some that belong in another, is designated to live in the higher stratum. Well factored applications tend to have a greater number of classes within the lower strata – indeed one objective of the ARM is to put a focus on this type of factoring – so there’s a clear implication here that if a package cross stata boundaries you should consider restructuring it. Applications with all or most of the packages in the higher strata – as per this rule – tend to be poorly factored – and often exhibit the problems discussed at the beginning of this article.

1.3. The rules (stratum specific)

  • Platform underpins the application development.
    Platform contains packages or utilities that are acquired externally to support the development. Typical examples include Jakarta Struts, java.lang.*, Swing, Microsoft Foundation Classes, .NET GUI libraries, etc. Although not discussed greatly in this article choice of Platform technology is a key to ensuring project success –hence its inclusion in this model.
  • Infrastructure neither contains nor depends on domain-specific code.
    Packages in Infrastructure are those which contain general purpose (non-domain specific) classes that provide utility functionality applicable to many different types of application. Infrastructure may only depend on (import from) platform. Typical examples include: general purpose object/relational mapping code (persistence); general purpose observer mechanisms; general purpose group based security mechanisms; and thin wrappers imposing a restricted API on Platform functionality.
  • Domain contains domain specific classes (often called entities).
    Packages in Domain contain domain specific abstractions that you’d typically find in an entity relationship or domain model. User and/or external system interface/presentation code is specifically prohibited. In the context of enterprise applications, Domain should provide the illusion (abstraction) that all domain classes are in memory, hiding the details of any persistence mechanism. Domain may also hide other Infrastructure concerns, where this does not introduce needless complexity.
    The upper domain stratum contains packages made up of “reference” objects – those typically persisted in their own right (with primary key, etc.) in a relational database. Typical examples include: a customers package (providing access/update functionality to the set of customers), an accounts package, etc. The lower domain stratum often contains packages that provide “value” objects – those that are typically contained by value as attributes of upper domain classes. Examples include: telephone number, date, money, etc.
  • Application provides a service oriented architecture.
    Packages in Application provide a set of application specific transactional services that the Interface stratum uses to query and/or update the application state. Application packages typically “wire-up” or provide the “linking glue” for decoupled Domain packages (see figure 4). The upper Application stratum will provide transaction services like: createCustomer, getAllCustomers, createAccount, getAccountsByCriteria, etc. Lower Application packages, if present, will contain utility sub-services that are non-transactional, but which are used to construct the transactional services provided by the upper Application.
  • Interface packages make the application do something!
    At its most fundamental, the purpose of the Interface stratum is to interact with the outside world, and at its request, call the Application stratum to change the application’s internal state. Most commonly the Interface holds packages that provide application specific user interface – that is - user interface classes that are particular to the application in question (not general purpose UI toolkit classes). Interface may also contain code to parse application specific file-interchange formats (e.g. a custom XML format), deal with requests from external systems (web-enabling code), or application specific timer related activities.
    The Upper Interface typically contains complete user interface dialogs such as the “create a new customer” screen, or the “find a video” screen. Lower Interface, if present, may contain application specific user-interface “widgets” such as a “find video by name” pane (one used on multiple screens), or an account-type drop-down listbox (containing values such as ‘current’, ‘savings’, etc).. Lower Interface widgets are thus used to build upper Interface user interfaces.

2. Video stores – a case study

That’s enough theory for the moment. I’ll now show you an example based on a video store application. Note that the apparently simple set of requirements – see figures 2 and 3 - are sufficient to pose some architectural complexity.

  • customers can register for email or text message alerts that tell them when a video is available to rent
  • customers can reserve a video by replying to a text message alert
  • customers can search for videos by partial title, and reserve a video (using the GUI)

Figure 2 – requirements fragment for iteration 3 of the video store application. The application has two type of user interface – one GUI based and one mobile phone based.
Figure 3 shows the associated domain model for this application.

figure3

Figure 3 – domain model for the video store application.

Figure 4 shows the architecture resultant from iteration 3 of the video stores project.

figure4

Figure 4 – detailed view of the architecture of the video store system, with strata superimposed. Package names also indicate the stratum of a package. One infrastructure package (SMS) has been culled from a previous project. The Persistence package (which manages the object/relational mapping in a domain independent fashion) is shown only in outline.

2.1. Interface

Interface contains three packages, each of which is responsible for kicking the application into life:

  • InterfaceReservationGUI – responsible for the UI for reserving a video,
  • InterfaceIncomingSMS – responsible for reserving videos in the event a customer responds to an SMS alert, parsing the SMS text and creating a reservation for the video.
  • InterfaceAlertControl – responsible for sending alerts periodically.

Notice that all three of these packages depend - due to either inheriting from a class or implementing an interface - either on Infrastructure or Platform packages. In all three cases control is passed to Interface code from Infrastructure or Platform code via a call-back (using a technique called Inversion of Control or Dependency Injection). This is not an uncommon situation, and occurs when code is factored to remove application and domain specific dependencies – leading to better code factoring and making the SMS package, in particular, usable in multiple contexts. The ARM gives context to this type of code factoring.

2.2. Application

Application contains four decoupled packages, each of which provides a set of services that can be used by Interface code. Note that ReservationServices code in figure 4 is used by the both the SMSParser and the InterfaceReservationGUI packages – each packages provides a different user interface to the same underlying functionality. This type of code factoring is a key motivation in the separating the concerns of Interface and Application.

2.3. Domain

Domain contains four packages – one for each entity identified in the domain model – each of which follows a common project pattern (see figure 5).

What is particularly noteworthy here is that each package in Domain is completely decoupled from the others. None of the Domain packages depend on each other: Videos don’t know about Customers, Customers don’t know about Videos, and perhaps most surprisingly Reservations (see figure 6) don’t know or depend directly on either.

figure5

Figure 5 – detailed view of the contents of the DomainVideos package. Videos provides the mechanism by which collections of video Keys can be returned to Application code. Individual Keys are then used to instantiated individual Videos. The other domain packages in this example follow this pattern.


figure6

Figure 6 – The DomainReservation package is completely decoupled from DomainVideo or DomainCustomer packages. This is achieved using a simple technique, namely by the Reservation class storing only the Keys of the reservedItem and the reserver. In this case it is up to Application code determine what it does with these Keys. A variety of other techniques can be used to achieve Domain package decoupling. For example, Application code may often wire up Domain packages using Adapters. Note that the Key class shown here is imported from the Persistence package (see figure 4).

The DomainAlerts package both keeps track of alerts and sends them when asked to do so. In figure 4, it doesn’t actually know the mechanism(s) by which an alert can be sent, so it provides an interface (of the Java type) which is implemented by ApplicationAlertServices. If only one alert mechanism were needed, this might be considered needless complexity. However, Alerts can be sent by email or SMS, so this design results in less code being required overall, and has the additional benefit of making automated testing an easier proposition (see figure 7). The design is now also inherently extensible.

It is not atypical to see examples of Domain packages having their behaviour customised, in this fashion, by Application packages – indeed, this is one of the drivers for the seperation of Domain and Application.

figure7

Figure 7 – TestDomainAlerts masquerading as an Application package – adopting a client code perspective. To test DomainAlerts in isolation, TestDomainAlerts provides a MockAlerter, which enables it to verify that alerts are being invoked correctly. This is an example of dependency injection in action.

2.4. Infrastructure

Infrastructure contains two packages. The InfrastructurePersistence package is worthy of an article in its own right – but for our purpose here let’s just say that it manages the interface to a relational database, exports the Key class (and probably a Versioned Key class) and Transaction (unit of work) class, and makes sure things are stored in the database. As shown, both Application and Domain classes rely on Persistence facilities directly. In particular each service exported by Application packages will use Transaction class facilities.

Of more interest to us here is the InfrastructrueSMS package. A package providing general purpose SMS facilities is an obvious candidate for inclusion in Infrastructure– and could be genuinely re-used across many projects. In the example shown, an SMSListener class is instantiated to listen on an appropriate number (i.e. the number on which we’re expecting to receive incoming text messages). Incoming messages are forwarded to the SMSActioner interface, which in this example is realised by the SMSParser class in the InterfaceIncomingSMS package. See figure 8.

NOTE: Enlarge the diagram below by clicking on the picture

figure8

Figure 8 – [1] An incoming SMS invokes a call-back to the InterfaceIncomingSMS package. [2.1] SMSParser.processIncomingSMS parses the incoming message to extract the Video ID to be reserved (contained in the message) and [2.2] looks up the Customer who sent the SMS by their telephone number, before finally [3] making a Reservation.

2.5. Platform

Platform contains the building blocks that underpin the whole development. If you assume the video stores application is written in Java, it will most likely use standard Java libraries to build the GUI, to interface to the database (JDBC), for the basic timing mechanism, and to send email.

3. The ARM revisited

3.1. A conceptual and practical model

As you can see from the above example, the reference model has been of conceptual - in terms of assisting in how to think about application structure - and a practical - in terms of concrete package sub-division visible in source code - assistance in getting us to a well structured, well factored application with a coherent and manageable set of dependencies.

Summarising:

The ARM (including its rules) prescribes how to horizontally sub-divide a large application into packages based on the responsibilities assigned to classes, and the natural dependencies that will exist between these classes.

3.2. A deeper look at the strata

So what exactly does it mean that one stratum is “on top of” another in this model? The ARM pulls together three threads of reasoning about good application structure:

  • Dependencies. Most simply understood is the issue of compile-time dependencies. Packages in a higher stratum import from packages in the same or lower stratum. Put another way, the dependencies always point downwards. Understanding and managing your dependencies is a key feature of flexible application architecture and the ARM is there to help you do this.
  • Functional specificity/neutrality. The higher the stratum, the more application specific and functionally powerful the operations provided, and the nearer you get to having a complete application. Put another way, the higher you go, the easier it should be to build your specific application with the facilities provided. Conversely, the lower you go, the more work you will have to do to build a specific application, but the more open the range of possible application you could write is.
  • Stability. The higher the stratum, the less stable (in the face of changing customer requirements) packages become. It’s far more likely that an Interface package will change than a Infrastructure package – assuming you’ve factored your packages correctly – and the whole point of factoring out domain specific functionality from Infrastructure is make it stable against change. There is a but here, however. Given the current state of programming language technology, changes in non-functional requirements (say, changing a single user in-memory application to a multi-user database application) can still pull our foundations out from under us. Hence the desire to tie-down non-functional issues as early as possible in a project lifecycle.
    Dependency management and functional power/specificity are integrally related concepts, for the pure and simple reason that we build higher level functionality out of lower level functionality - and hence depend on it. Stability is a by-product of factoring out “higher” level functionality that is more likely to be subject to change. In combination, these factors make the ARM a powerful weapon in the enterprise architects armoury.

3.3. Why five strata?

As you’ve seen, Platform is the home of the technology base that underpins your application development. Getting your Platform components right can be a project in its own right – hence its inclusion in the ARM - even though it has a lot of similarities with Infrastructure. The distinction between Platform and Infrastructure is, however, very clear: If it is externally sourced and non-domain specific – it’s Platform. It’s written internally, it’s Infrastructure - again, assuming no domain dependencies have crept it.

The distinction between Interface and Application is also fairly clear cut – if a class is directly related to application specific user interface – then it’s Interface code; if some code is there to deal with external system integration e.g. web-services – then it’s Interface; if the code is dealing with parsing an application specific file format, e.g. an application specific XML format, then it’s Interface. But note here, in the same way that user interface code splits into general purpose (Platform) and application specific (Interface), so most XML parsers are Platform. XML itself is domain-neutral – but specific DTDs will require custom Interface code to be written to interpret domain-specific concepts.

The distinction between Application and Domain is often a source of questions. As I said earlier, Application code is there to provide a set of transactional services for Interface code to use. Domain code is there to provide domain-level abstractions like Book, Account, and so on, which are then combined by Application code to provide application functionality. Without the Application stratum the decoupled Domain packages would remain forever decoupled – and there would be no application!

The distinction between Application and Domain becomes even more important in the context of product line architectures – i.e. when you are developing a family of inter-operating, inter-related applications. Consider the Video store application discussed earlier. Suppose as well as the in-store systems (discussed above), there is also a head-office system. Shouldn’t it be possible to re-use some of the Domain packages – perhaps Videos, say for central stock-control purposes?It certainly should. In this context, the Domain packages would be used in both applications, but the Application code would be different – as there would be different functional requirements driving that development.

4. Any questions?

  • We do agile development, this seems like big up front design... Design guidance and development process are orthogonal concepts. You might arrive at an architecture by months of forethought, or you can let the ARM assist you in evolving your architecture in an incremental fashion. For the purposes of this article at least, that’s up to you!
  • What about the cost (in code terms) of all these strata? The ARM is about packaging. Every line of code you write using the ARM should be absolutely necessary to meet the needs of your customers, the needs of automated testing, and the needs of delivering a high-quality well-factored application that shows intent at a code level. You don’t have to write any code because of the ARM – you just have to put the right code into the right package!
  • Dependencies seem to go right across the strata, shouldn’t there be a rule saying one stratum can only use the stratum below it. No. Consider the ReservationVideoButton in the example above – which inherits directly from the PlatformGUI package. Applying a rule like this, it would be necessary for each of the three intervening strata to provide a wrapper hiding the facilities provided by the stratum below it. To mandate this would be to mandate what is potentially needless complexity.
  • Is Persistence always Infrastructure / Platform? No, not always. It takes some considerable effort and skill to build a general purpose persistence mechanism, and this may be overkill for a single project. In such circumstances, it is not uncommon to write domain-specific persistence mechanisms (sometimes in the form of brokers for various domain classes). This will tend to lead to some code duplication (it’s the duplicate code you would ideally factor out into an Infrastructure package) – but may be necessary. It really does depend on circumstances.
  • Can the ARM be used in conjuction with code generation? Code generation, for example generating domain-specific persistence code in the EJB style, is really an orthogonal concern – but it can be the cause of confusion. If you find you are getting confused – take a look at the code that is generated and see where it fits into the ARM.
  • What about vertical sub-division of the application – the ARM only deals with horizontal sub-division, doesn’t it? That’s right. As I alluded to earlier, there is a 1-to-many relationship between a stratum and the packages it contains. For completeness, you need some sort of guidance in this sub-division. Help is at hand here in the form of two principles of packaging:
    • The Common Closure Principle or CCP [RMartin], which states: “package things together that change together”. DomainAlerts contains the Alerts, Alert and AlertSender packages precisely because the are highly inter-dependent (highly coupled), and a change to any of them is likely to affect the others.
    • The Common Re-use Principle or CRP [RMartin], which states: “package things together that are used together”. ApplicationAlertServices (see figure 4) contains three classes which are apparently (from the perspective of the relationships within this package) unrelated. A closer inspection, however, reveals that AlertServices.SendNecessaryAlerts method cannot be used without supplying one or more concrete AlertSenders – in the example shown the EmailAlertSender and SMSAlertSender classes. QED the common re-use principle applies.
  • What practical steps can I take to apply this model on my project? It’s remarkably simple to use the ARM to keep track of your project’s package structure. All you need is a large whiteboard, which you divide into five horizontal sections as per the stata. Then, draw a package symbol with the package name on the board for each package in your system. Show the dependencies between packages as arrows - all going downwards, of course. This type of diagram is known as a package map.

5. Summary

In this article I have presented an architectural reference model that I’ve used successfully on a number of Enterprise Applications. The model and its rules are intended to assist you in improving the structure of your code, in particular to ensure greater clarity of responsibility at the package level, to improve overall code factoring, to reduce duplication within code and to enable you to manage your package dependencies effectively.

Coming full circle to the problems that introduced this article:

  • the ARM encourages repetative code to be pushed down into a lower stata. Repetative Application code into Domain or Infrastructure (depending on whether it has domain dependencies or not); repetative Domain code into Infrastructure, etc.;
  • this in turn leads to less duplication making functional changes simpler;
  • package structure is now based on a more coherent set of rules, so it should be easier for new staff to work out what code will live where;
  • keeping a package map on the wall means intelligent work scheduling is easier;
  • automated testing and subsequent bug fixing is easier, as dependencies have been brought under greater control, and code factoring has been improved by putting the right code in the right statum;
  • the application is now more stable – pushing non-domain specific code down into infrastructure means it won’t be affected by functional changes, and pushing repetative Application code into Domain leads to greater localisation of changes in the face of varying functional requirements.

You should find out more about the ARM by looking:

6. References and credits

[RMartin] – Granularity, Robert C. Martin - C++ Report, 1996.

Thanks are due to Hubert Matthews, who worked with me on previous papers on this topic, and to Andrew Vautier and Anders Nestors of Accenture – on whose 1 Million+ line C++ banking project provided much seed material for the concepts discussed here.

Thanks also to Ilja Preuþ, who reviewed earlier drafts of this article, and whose comments were invaluable.

You might also like...

Comments

Contribute

Why not write for us? Or you could submit an event or a user group in your area. Alternatively just tell us what you think!

Our tools

We've got automatic conversion tools to convert C# to VB.NET, VB.NET to C#. Also you can compress javascript and compress css and generate sql connection strings.

“Beware of bugs in the above code; I have only proved it correct, not tried it.” - Donald Knuth