Steve’s 2nd Law of Good Software Architecture
My first rule of good software architecture dealt with ways of making a particular code-base last a long time. The focus of the 2nd rule is different – it assumes that a problem domain will be solved multiple times by different software, or multiple versions of the same software. It suggests ways that we can make each new re-solving of the problem easier than the last.
To review, my 2nd law of good software architecture is:
Keep as much information as possible in an accessible, declarative form. This will eliminate duplication, and enable your software to be discarded and re-written without losing quite as much
This law is all about re-use of information, and describing how a particular problem domain can become better understood, even to the extreme where the “software” is just data.
We’ve all seen the tool-sets for generating entire applications – enter your requirements (mostly just your data structure) using vendor X’s WonderMaker(tm) and lo and behold, out springs an application with handy generated forms for doing wonderful things. As it turns out, those wonderful things are pretty much Create, Read, Update and Delete. Not so useful after all.
Those generic tools do solve particular problems well – but it is usually not the problem we want to solve. Understandably, users demand more than just create, read update and delete – they want to use the software to perform some task that meets their goals.
We can achieve the goal of a tool that generates most of an application, but only once we understand the problem domain well enough. We need to understand the domain, because we need to know what we can generate, and what we need to leave open to extension.
It is always a mistake to design a v1.0 system where logic is executed based on models of application logic. There are plenty of horror stories about the architect who thought he could model the business logic using XML. Don’t be the next one. This post is about evolving your understanding of a particular domain to the point where you can create models with confidence they will work.
That said, even in version 1.0, there are some things we can recognize. The first is that there are at least two easily identified models of the system. From the user’s perspective, there is the model that they understand and interact with. At the other end, there is the database. The important logic of the application sits between the user’s model and the database. This is the origin of the old 3-tiered concept – UI + Application + Database.
What we have to realize is that we can model each of these things in a way that is declarative. In version 1.0, we may not understand the way the user wants to use the UI well enough to do much work in this regard. However, we can certainly model the database in declarative form, and have that model persist after version 1.0.
Modeling the Application layer is the last evolutionary step. You will not reach that point until the core application requirements are stable and well-understood.
For existing products, the process of modeling requires a re-write of some portion of the system. This is unavoidable, because you have to extract information from where it is hidden in the code, and represent it outside of the code. The code will no longer work. The good news is that once you have correctly modeled a part of the system, the model can be extended to capture new types of information, and need not be re-written again.
A re-usable database Model
So what does a re-usable database model look like?
- It treats relationships as a first class concept – they have names, and they have attributes (one-to-many, cascade-delete behavior).
- It describes fields in a rich, descriptive manner. Strings have maximum lengths, phone numbers are represented by a phone-number data type, etc.
- It describes lookups (sets of values that are acceptable for a field)
- It describes roles – how field values come together to represent a particular flavor of record that has meaning to the user. A particular flavor may be extended with additional fields and properties.
- It should be directly and easily accessible to the rest of the code (re-usable).
- It should be able to be transformed into something that the data access layer (or ORM tool) can use directly.
- It should be able to be transformed into an empty database (it is complete).
The most obvious storage form of the model is as XML, because it is very accessible, and because it can represent hierarchical data. Other forms are ok, as long as they meet the above criteria.
Why all of the richness? We want to capture as much information as possible in a single place. This allows us to make use of that information at higher layers of the application, in ways that enhance the user and the developer experience. DRY (Don’t Repeat Yourself) is a powerful architectural technique.
Not all database structures represent the model we wish we had. We may have inherited a database, and it may be a horrible thing to behold. My first law of good software architecture applies – since we are exposing the model directly to the developer, we want it to be the one we wish we had. If the real database is too far from what we want, then we need to take steps to address that inconsistency. To do otherwise is to invite artificial complexity in the application code.
A re-usable UI Model
As mentioned previously, I do not expect that many version 1.0 products have a very good UI model. Still, if we can understand what a UI model looks like, then we can work towards it.
Firstly, a UI model has a relationship with the database model. The relationship is mapped – i.e. there is some automated transformation that can be used to relate a field on the UI back to one or more fields in the database. This is important, because the relationship is what allows us to re-use information defined at the database model (such as rich data types, lookups, maximum field lengths etc). If we’re re-using information, then we are not duplicating it.
A UI model can grow in pieces. First, you can model screens, then larger pieces that describe how various screens fit together. Screen models are the easiest. (Even today, many applications make use of screen models).
Again, XML is a good choice for representing the UI model.
Beware of including layout information in the UI model. That is a different aspect that belongs in a different model. The primary purpose of the UI model is to bring together fields and screens in a way that represents how the user sees them. This may include their likely order on the screen, but should not include their actual co-ordinates.
UI models are re-usable in several ways. Security, Form Design, and Ad-hoc user queries are a few.
Layout of Forms (Views)
Form layout can be defined declaratively, but it is seldom worth the trouble to do that manually. We cannot predict the next evolution of UI well enough to design a representation that is good enough. The best you can probably do is favor form-design tools that save themselves declaratively (for example, XAML).
Your form layout should make use of the UI Model directly (via data binding and control-binding). Otherwise, you are just duplicating yourself. (Control-binding is the technique of having the final appearance of a particular control determined based on metadata. See the screen shots in my Egg UI post for
an example).
Form layouts can be generated. This is a dangerous path, because it can limit your ability to satisfy the needs of the end-user.
Security
It is particularly useful to relate security to the UI model. One reason is that security is highly contextual – whether a user has rights to touch particular data elements can be driven by many factors, including the time of day. Another reason is that users need to understand security in order to effectively define it. The UI model’s shared understanding of the user’s perspective allows a good point of interaction for security.
Ad-hoc user queries
Often, we may want to expose the ability for users to query a database in some way that is fairly dynamic. A UI model that is mapped back to the database model provides a simple way to provide that feature.