There is a lot of information on the Internet about the three-tier architecture, three-tier structure and other names for the concept of breaking a program into tiers. The current system design paradigm is to break your software into APIs and the three-tier architecture still applies. I’m going to try and explain the three-tier architecture from the point of practicality and explain the benefits of following this structure. But first, I have to explain what happens when a hand-full of inexperienced programmers run in and build a system from the ground up…
Bad System Design
Every seasoned developer knows what I’m talking about. It’s the organically grown, not very well planned system. Programming is easy. Once a person learns the basic syntax, the world is their oyster! Until the system get really big. There’s a tipping point where tightly-coupled monolithic systems become progressively more difficult and time consuming to enhance. Many systems that I have worked on were well beyond that point when I started working on them. Let me show a diagram of what I’m talking about:
I often-times joke about how I would like to build a time machine for no other reason than to go back in time, sit down with the developers of the system I am working on and tell them what they should do. To head it off before it becomes a very large mess. I suspect that this craziness occurs because companies are started by non-programmers and they hook-up with some young and energetic programmer with little real-world experience who can make magic happen. Any programmer can get the seed started. Poor programming practices and bad system design doesn’t show up right away. A startup company might only have a few hundred users at first. Hardware is cheap, SQL server licenses seem reasonable, everything is working as expected. I also suspect that those developers move on when the system becomes too difficult to manager. They move on to another “new” project that they can start bad. Either that, or they learn their lesson and the next company they work at is lucky to get a programmer with knowledge of how not to write a program.
Once the software gets to the point that I’ve described, then it takes programmers like me to fix it. Sometimes it takes a lot of programmers with my kind of knowledge to fix it. Fixing a system like this is expensive and takes time. It’s a lot like repairing a jet while in flight. The jet must stay flying while you shut down one engine and upgrade it to a new one. Sound like fun? Sometimes it is, usually it’s not.
In case you’re completely unfamiliar with the three-tier system, here is the simplified diagram:
The connection between the front-end and back-end is usually an API connection using REST. You can pass data back and forth between these two layers using JSON or XML or just perform “get”, “post”, “delete” and “put” operations. If you treat your front-end as a system that communicates with another system called your back-end, you’ll have a successful implementation. You’ll still have hardware challenges (like network bandwidth and server instances), but those can be solved much quicker and cheaper than rewriting software.
The connection between the back-end and database has another purpose. Your goal should be to make sure your back-end is database technology independent as much as possible. You want the option of switching to a database with cheap licensing costs. If you work hard up-front, you’ll get a pay-back down the road when your company expands to a respectable size and the database licensing cost starts to look ugly.
What About APIs?
The above diagram looks like a monolithic program at first glance. If you follow the rules I already laid out, you’ll end up with one large monolithic program. So there’s one more level of separation you must be aware of. You need to logically divide your system into independent APIs. You can split your system into a handful of large APIs or hundreds of smaller APIs. It’s better to build a lot of smaller APIs, but that can depend on what type of system is being built and how many logical boxes you can divide it into. Here’s an example of a very simple system divided into APIs:
This is not a typical way to divide your APIs. Typically, an API can share a database with another API and the front-end can be separate from the API itself. For now, let’s talk about the advantages of this design as I’ve shown.
- Each section of your system is independent. If a user decides to consume a lot of resources by executing a long-running task, it won’t affect any other section of your system. You can contain the resource problem. In the monolithic design, any long-running process will kill the entire system and all users will experience the slow-down.
- If one section of your system requires heavy resources, then you can allocate more resources for that one section and leave all other sections the same. In other words, you can expand one API to be hosted by multiple servers, while other APIs are each on one server.
- Deployment is easy. You only deploy the APIs that are updated. If your front-end is well isolated, then you can deploy a back-end piece without the need for deployment of your front-end.
- Your technology can be mixed. You can use different database technologies for each section. You can use different programming languages for each back-end or different frameworks for each front-end. This also means that you have a means to convert some or all of your system to a Unix hosted system. A new API can be built using Python or PHP and that API can be hosted on a Linux virtual box. Notice how the front-end should require no redesign as well as your database. Just the back-end software for one subsection of your system.
Converting a Legacy System
If you have a legacy system built around the monolithic design pattern, you’re going to want to take steps as soon as possible to get into a three-tier architecture. You’ll also want to build any new parts using an API design pattern. Usually it takes multiple iterations to remove the stored procedures and replace the code with decoupled front-end and back-end code. You’ll probably start with something like this:
In this diagram the database is shared between the new API and the legacy system, which is still just a monolithic program. Notice how stored procedures are avoided by the API on the right side. All the business logic must be contained in the back-end so it can be unit tested. Eventually, you’ll end up with something like this:
Some of your APIs can have their own data while others rely on the main database. The monolithic section of your system should start to shrink. The number of stored procedures should shrink. This system is already easier to maintain than the complete monolithic system. You’re still saddled with the monolithic section and the gigantic database with stored procedures. However, you now have sections of your system that is independent and easy to maintain and deploy. Another possibility is to do this:
I need to explain a little more about the front-end that many programmers are not aware of. Your system design goal for the front-end is to assume that your company will grow so large that you’re going to have front-end specialists. These people should be artists who work with HTML, CSS and other front-end languages. The front-end designer is concerned with usability and aesthetics. The back-end designer is concerned about accuracy and speed. These are two different skill sets. The front-end person should be more of a graphic designer while the back-end person should be a programmer with knowledge of scalability and system performance. Small companies will hire a programmer to perform both tasks, but a large company must begin to divide their personnel into distinct skill-sets to maximize the quality of their product.
When you connect to a database the common and simple method is to use something like ODBC or ADO. Then SQL statements are sent as strings with parameters to the database directly. There are many issues with this approach and the current solution is to use an ORM like Entity Framework, NHibernate or even Dapper. Here’s a list of the advantages of an ORM:
- The queries are in LINQ and most errors can be found at compile time.
- The context can be easily changed to point to another database technology.
- If you including mappings that match your database, you can detect many database problems at compile time, like child to parent relationship issues (attempt to insert a child record with no parent).
- An ORM can break dependency with the database and provide an easy method of unit testing.
As I mentioned earlier, you must avoid stored procedures, functions and any other database technology specific features. Don’t back yourself into a corner because MS SQL server had a feature that made it easy to use as an enhancement. If your system is built around a set of stored procedures, you’ll be in trouble if you want to switch from MS SQL to MySQL, or from MS SQL to Oracle.
I’m hoping that this blog post is read by a lot of entry-level programmers. You might have seen the three-tier architecture mentioned in your school book or on a website and didn’t realize what it was all about. Many articles get into the technical details of how to implement a three-tier architecture using C# or some other language, glossing over the big picture of “why” it’s done this way. Be aware that there are also other multi-tier architectures that can be employed. Which technique you use doesn’t really matter as long as you know why it’s done that way. When you build a real system, you have to be aware of what the implications of your design are going to be five or ten years from now. If you’re just going to write some code and put it into production, you’ll run into a brick wall before long. Keep these techniques in mind when you’re building your first system. It will pay dividends down the road when you can enhance your software just be modifying a small API and tweak some front-end code.