Designing Layered Architecture (1/3)
Defining scalable applications that embrace change
Today we’re going to start on a journey of exploring layered architecture — a style used to structure medium-large applications or microservices that require flexibility and tolerance for change requests.
I’ve had the chance to hold a number of interviews for software development positions over the years and beyond the essential questions regarding good code structure (SOLID, design patterns) and testability, I like to ask some questions regarding architecture, and on how the candidate would design a simple piece of software for an enterprise customer.
One of the common answers that I’ve received is that people would use an MVC architecture in order to have clean separation between request processing, database interaction and the user interface.
Today I would like to look at why, for enterprise customers or larger projects, we may want to look at expanding this paradigm, and how to approach layered architecture if you’ve only encountered MVC so far.
Also, please note that we won’t be looking at the actual components of layered systems in this article — similar to C4 diagrams, I want us to focus on the context first, and understand why there is need for a system that’s more tolerant to change, and when there is need for it.
Advantages of MVC
While I personally consider MVC to fall more into the realm of design pattern than software architecture, we’re generally going to be referring to MVC frameworks, such as Ruby on Rails, Django and CodeIgniter. Let’s look at the some of the advantages of the model that they’re proposing:
- Known by a large segment of developers, meaning that it’s easy to find people to understand and maintain a software project.
- On-boarding is also easy, and there’s generally lower overhead especially when moving people between technologies.
- Makes a clear distinction between the backend datastore (ie. database), the business logic and the visual interface that is displayed to the user.
- Low complexity for handling change requests, generally leads to faster short-term development.
Depending on the needs of your project, the above might absolutely be enough for your team, and it’s important to keep things as simple as possible if you can. This is especially important in case you value mutual code ownership and want to migrate developers between multiple projects and technologies.
Common needs of enterprise applications
Enterprise applications generally involve one or multiple cross-technology engineering teams and usually have planned development and maintenance time spanning multiple years.
As far as the experience of our team has been, they also usually feature a set of common needs:
- Data independence: data should be easily structured in different backend services (relational database, NoSQL, API, etc.) and there should be a small overhead of migrating between different vendors of the same class (ie. replacing Redis with Memcache).
- Maintenance: it should be easy for the engineering team to execute change requests on the software in order to respond to market events or to change behaviour in the existing codebase.
- Scalability: it should be easy to scale out any component from the stack in order to support higher load as the needs of the system change.
- Integration: it should be easy for the application to integrate with a variety of third-party services, and to be able to interact with HTTP browsers, API calls from other devices (phones, tablets, TVs etc.), system daemons, socket connections from IoT devices.
- Testability: Individual workflows from the application need to be easily testable, preferably supporting the entire suite of unit, integration, functional and acceptance tests.
While it might seem that the approach for an enterprise application and a traditional web application should be significantly different, we can basically sum all of the above in one concept: supporting change.
Common issues with MVC frameworks
MVC frameworks suffer from a few common pitfalls, and I’m sure that some of you will recognise them:
- Hard to maintain view abstractions, as there usually is a tight coupling between the models, entities and data retrieval.
- It’s generally more difficult to reuse business logic, as this too is tightly coupled with the controller concept. This introduces some problems, particularly when we want a web application to move outside managing only HTTP requests, and integrate with system daemons or socket connections.
- Difficult to handle models that are split between multiple data stores (ie. Redis, a NoSQL database and a relational database), which also means that it’s more difficult to introduce immutable data structures.
- From an architectural perspective, MVC allows the view to exchange data with both the model and the controller, which can lead to issues when trying to test critical flows, and also hard to reproduce bugs.
When to consider migrating?
There’s an important aspect to the strictly MVC approach, which is the time of life of said project. If an application is designed to live for a relatively short period of time and not feature many change requests, then there might not be a point in adding complexity to your stack.
In the case of a startup, we’ve found that it doesn’t make sense to add complexity early on — focus on defining your product, testing the market, and changing it so that you it makes sense for your customers. MVC frameworks are a great starting point, since they offer just enough structure so that you can eventually refactor the solution into something more robust, such as layered/hexagonal architecture, or eventually a microservice architecture, but only once you’ve iterated on your business idea.
In the case of a software house, this definitely depends on the needs of the customer and what their expectations are for the product going forward — are they also just testing the waters, or are they looking to make the product a core part of their business? Do they work in an enterprise environment? What code coverage percentage are they looking for? Your project architecture should always match the customer’s expectations, fault tolerance and vision of the future.
We’ve found that regardless of the architectural style, the biggest win comes from one consistent behavioural trait: transparency.
The more involved the stakeholders and the more they know about the limitations of the product, the better they will be at predicting the future and managing their expectations.
If a web application has been developed as an MVP in a few weeks, they might expect that moving the business to a native mobile platform will require some additional steps, including an overhead for refactoring the initial project and exposing REST endpoints.
On the other hand, they should be informed that once this process has been finished, it can be easy to integrate the platform into a native desktop or a Smart TV application, so that they can look at evaluating new revenue streams.
In the following articles, we’re going to have a look at defining a greenfield MVC design, and then refactoring it to easily support change requests through a layered system.
- Part 2 — Isolating data stores and structuring the data transfer process;
- Part 3 — Reusing business logic and exposing our application to a larger ecosystem;
We’re also going to be looking at the main actors of a layered application: the Data Access Layer, Data Transfer Object, Business Logic Layer and View Abstraction Layer.
Thank you for reaching the end — if you’re excited to know about something in particular, or if you have any questions, please let us know in the comments section. Also, if you’ve enjoyed this read, make sure to follow our Medium publication for more articles.