Dividing software into separate layers or tiers is one of the most fundamental principles within software engineering. We all know the beautiful schemas vendors create in order to show the modularity of their software. And anybody will agree that a layer can abstract away a lot of underlying complexity to the rest of a system. That is why we tend to call them abstraction layers.
The question, however, is whether the layers and tiers in a typical information system based on a service-oriented architecture (SOA) can be regarded as abstraction layers. We all know the concepts of a data layer, a service layer, and client applications with control and view layers. But how fundamental are these layers to developing an information system?
Of course, it makes sense to put all database access in a separate database or data layer. Due to the impedance mismatch between databases and third-generation languages (3GL, like Java, C#, and others) it is impossible to get things done without a lot of code to glue things together. Every read or update action requires composing a query, supplying it with parameters, and parsing the result afterwards. And it is good practice not to mix this up with actual business logic like calculations, combining data, etc. So, mainly due to the impedance mismatch, there seems to be a role for a data layer, in whatever shape.
It could be argued, however, whether passing around data by itself can be called an abstraction. In a typical SOA architecture, 80% of the code we write in any layer does nothing more than receive the data coming in before passing it through in a slightly different format. Let’s say we store some customer data, like name and birthdate, in a database and later retrieve it for display on the user’s screen. We can invent as many ways to represent this data as we like (e.g., fields in a database, in-memory structures, JSON elements, HTML text), but in the end all these variations still represent the exact same customer data. Imagine we calculate a customer’s age given his/her birthdate. In doing this we add extra data (the age) to the model. But because it is only an addition to the data model, it doesn’t replace the original data (the birthdate). There may be screens on which the age instead of the birthdate is shown, but there will still be screens showing the birthdate itself, if only so that its value can be checked and edited.
What this shows us is a fundamental difference between layers in a SOA (or similar) architecture and abstraction layers in a technical sense. If you write a TCP/IP stack you will probably use (part of) the OSI network model to split the intrinsic complexity into different layers. Each layer in this model handles a piece of this complexity. It is just not easy to manage all the complexity of receiving electronic pulses over a network cable and maintaining a reliable error-corrected connection within a single layer. The OSI layers make sure that the abstraction from one concept to another is taken care of step by step. Each layer solves a problem that the next layer does not need to address: buffering, error correction, reordering packages, sessions, etc.
Another example can be found in the way graphics cards are controlled from within a game. This happens in a number of steps. The core of a game keeps track of the state the game is in. But before any pixels are shown on the screen, a few abstraction layers make sure virtual objects are translated into a 2D image.
Programming languages themselves are also full of such technical abstractions. For example, take the fact that you can create objects in an object-oriented programming language and that these objects are automatically garbage-collected when needed. The mechanism that takes care of this is hidden away below the surface. In that sense this again is a real software abstraction. In this case, as something that makes it easier to program in term of objects.
The problem with layers in an information system (SOA-based or not) is that they are a mix of different things. On the one hand, they are involved in technical matters like getting access to a database, the exchange of data over network connections, and the conversion of data from one format to another. On the other, they have code that represents actual business logic: evaluating expressions to derive data, processing updates, and checking constraints.
While actual business logic is by definition application-specific, we can imagine a much more generic method for handling the above-mentioned technical aspects, generic behavior that directly follows from the underlying data model. Because of the shortcomings of current programming languages and databases, we are forced to mix up these technical aspects with business logic, resulting in a lot of manually written code passing all the data through all the layers of a system. If we can devise the right abstractions to separate technical matters from business logic, we can create an environment in which CRUD (Create, Read, Update, and Delete) requests will be handled implicitly and in which we only have to write code for actual business logic. The platform itself can then take care of network communication and persistency.
Obviously, this sounds a lot like the main goal of model-driven development (MDD). But although MDD itself proves that we can get by with a lot less code writing, the point here is that we should be able to this in a more fundamental way, without code generation (see Code Generation is a Poor Man’s Compiler). We could have a runtime environment that abstracts away network communication and persistency as some 4GL and RAD environments did in the nineties, but with today’s knowledge, more flexible and internet-aware. We see some elements of this in the rising low-code trend (Mendix, OutSystems, and others), but we are not there yet.
The end goal should be to abstract away runtime aspects in an application-agnostic platform instead of requiring application developers to handle all that in application-specific layers.