First of all, there is no 'universally correct' architecture that suits every scenario. The architecture may and should vary, based on project driving forces and requirements.
This article is almost about Application Architecture. I have seen people shooting questions like "Do you think we can use dependency injection?","How to de-couple the layers?", "Where I really need to put the business logic?", "Well, do we need to have separate business objects and DTO objects?", "Shall we go with Service oriented architecture?" etc. The answer is "Yes or No, based on your scenario" - Once you have the project structure in place, and once developers know where to write the code for a particular module or functionality, it is pretty easy to go ahead with the project.
This article is expected to discuss some practical concepts you may employ, when you decide the architecture of your solution. There is no rocket science here - the whole objective is to aid you to decide an architecture that may suit your scenario. Also, you may find a lot of points in this article where you may opt to disagree. You are free to do that, and you are welcome to post your comments to suggest an alternative approach or to trigger a good discussion :). I reckon that some technology related scenarios are mainly from the microsoft technology side, because I'm mainly working in the .NET space
First things first. I just need to break the myth that Application Architecture and design is driven by technology. Technology should be chosen only to implement an architecture in the best possible way, and not vice versa. Also, architecture, in my opinion, is an ongoing process. The objective of architecture is to bring the solution closer and closer to the user expectation, and to unify diverse systems to provide standardization. Architecture is an ongoing process because, user expectations may change over time, as users thrive for better systems, models, and better experiences.
Here is one of my favorite cartoons :)
Some factors that may affect your architecture include
- Politics with-in/between the organizations and stake holders (believe me, this is one of the critical factors)
- Available resources and in house expertise
- How the system is expected to be consumed, and deployment considerations
- Non functional requirements like Scalability, Availability, Performance, Security etc
- Project related driving forces like Cost, Scope, Quality and Time
- Re-usable components/entities available
- Various risk factors (like unseen client expectations and change requests)
If you are an architect, you know that you are already squeezed up between one or more of the above forces. Here we go :)
Have you ever got involved in a migration project? You are on the receiving side!!. In most cases, the guys on the other (legacy) side - i.e, the guys who are expected to support you in a big way by transferring knowledge about the existing system - is reluctant to do that. Simply because once the migration or take over is done, then they may go out of job, or they'll lose their significance in the organization.
So, in the end, you are forced to move to the design phase - with out actually understanding the existing system to be migrated as a whole. An alternative might going for an iterative model, but still this is just one example of how politics can be a major factor that affects your architecture.
Available Resources And In House Expertise
I beleive that architecting a solution should be a team based activity. Atleast, there should be an architecture approval work flow to ensure that the architecture handles basic non functional requirements like scalability, extensibility, security etc.
Building in house expertise and documenting the success and failure factors are important, so that expertise can be re-used. To do this, one suggested solution is to form forcus groups, on areas like security, performance etc - to ensure that best practices are followed in the architecture and design. These teams should also evaluate the architectures of successfully implemented projects, to abstract best practices from the same. The success in re-using expertise across teams and building in house expertise is a major factor that affects the final architecture.
How the system is expected to be consumed, and deployment considerations
How the system is expected to be consumed by users is another major factor that affects the final picture. Over a period of time, we have various considerations like Client Server, Distributed, Service Oriented etc, to name a few.
Example - 1: You are expected to architect a web based system to show the current temperature to the user (very simple, huh). The user is expected to view the temperature using his browser, by visiting your website. After one month, the client you are working for, needs to sell this as a service to other websites too, so that other websites can also use their service and show the temperature in their web pages.
Example - 2: You are expected to architect a web based shopping cart. Right now, you may deploy the Presentation Layer (UI) and business layer together in one server (i.e, the web server and application server will be one box), but you have to provide an option so that in future, when the user load increases, the business logic can be deployed in another machine (a seperate application server)
Example - 3: You are expected to architect a web based shopping cart. Right now, you may be using application level caching. But in future, when your load incrases, and when you go for a web farm scenario, how to make sure that the cache is shared across all servers
Non functional requirements like Scalability, Availability, Performance, Security etc
Non functional requirements are orphan kids - ignored by both the clients and the delivery team. Most times, a performance tuning initiative or security push initiative is kicked off only during the final phase of the project as a life saver.
Though NFRs are mostly ignored, they are one of the critical factors that has a direct impact on choosing the correct architecture. Wrong decisions are induced in architecture mostly because NFRs are ignored at earlier stages
As I mentioned earlier, focus groups can contribute in a big way, to ensure that NFRs are cosidered properly during each phase of the development life cycle.
Project related driving forces like Cost, Scope, Quality and Time
Some time back, when I was talking to one of my senior managers, he pointed out that it is not possible to give all the four decision factors (Cost, Scope, Quality and Time) to the client to decide on. The client is expected to compromise on one factor. It is not because the client has to compromise. It is simply impossible to keep all these four decision factors as non-varying factors - when a project is considered. If some one is choosing scope, quality and time - then the cost will be the varying factor. Otherwise, if some one is choosing Scope, Quality and Cost, time will be the varying factor
Unfortunately, this is not conveyed properly to the client, and the client will always end up choosing the cost, scope and timeframe. As a result, this will hit the quality of the project - because quality is the only remaining factor
The architect will end up in compromising on factors like extensibility and other NFRs, to meet the dead lines
Reusable components/Entities available
Reusability is the key. Shameless reuse of quality components and best practices should be enforced in the design and architecture stage. The availability of re usable components, factories and best practices is one major factor that affects the architecture.
It is the responsibility of the organization to ensure that experience and best practices are documented, transferred and reused properly
Unseen Client Expectations and Change Requests
One major success criteria for an architecture is, it should not break when new changes are accommodated. Having said that, this is not the case in most scenarios. These days, most companies are very agile in their strategies. Hence, client companies may raise change requests often - which are triggered due to the changes in their business model.
The requirement analysis may miss out client expecations, which may cause serious architecture level issues later. Even worse, the clients may expect the product to satisfy all their needs, by default. These expectations may also trigger change requests later.
Approach - The Thought Process
Assume that once you have the birds eye view of what needs to be done, a typical common starting point is to identify the tiers you need to have. For example, for a typical application, you may end up with something like
Data Tier <--> Data Access Tier <--> Business Tier <--> Presentation Logic Tier <--> GUI
Now, a lot of questions will start arising. You'll start thinking about how these layers communicate each other, how the data is passed up and down, how the data is persisted, how the layers are connected etc. Of course, the answers depend largely on the requirements and other driving forces we discussed above.
You might also think about various services you may use across tiers - like logging, caching etc
Along with this, you may also think about how the factors like performance, availability, security etc are relevant and applicable in each layer.
Tiers in the system
Let us have a brief look at each of the tiers first. We'll go bottom up
The data tier is where you persist the data, mostly the data base. The data tier expected to provide functionalities like storage, retreival, indexing and querying of data. When you consider the data tier - as I mentioned earlier - it is important that factors like transaction support, scalability, availability, security etc are considered.
Data Access Tier
Here, often you have the stuff to interface with your data tier. Two major factors you may need to take care is the scalability and response time. It is imperative that you should have proper strategies to make sure that the response time is good enough.
For example, you may need to cache the data to avoid hitting the database each time. Another popular strategy is to pool your connections to the database. Normally Data Access Tier is stateless
Sure enough, your business tier is there to apply business rules and transformations on the data, and to perform calculations. You may include business rule based validations in your business tier. In most scenarios, you might have a set of business objects for your business tier to operate on. Ideally, your business objects represent a domain specific model of your data.
The business tier receives data from the presentation tier in the form of business objects, perform required business rule validations and transformations, and call the required methods in data access tier to perform operations like storing and fetching. In most cases you may need state management in the business tier. For example, in an bookstore application, the business tier handles logic like adding books to the cart, calculate total cost etc.
It is certainly possible to implement services like caching and object pooling in the business tier also, based on the scenario.
Some other practical thoughts - from an architect's point of view, it is ideal to place all your business logic in the business layer. But some times, you have to do some trade offs for performance - and move a little bit of business logic to your stored procedures :). This decision depends on several factors - The primary factor is the size of data your application is expected to process. How ever, the key is to make sure that your stored procedures can be ported to some other database platform in future, with minimum overhead. Most database platforms are well evolved - and can handle load much better than best possible business layer design and algorithms you may decide to use, for processing data
Ideally, you should distribute the load wisely between all the tiers involved.
Presentation Logic Tier
The presentation logic tier handles the presentation logic. For example, for a web application, the presentation tier holds asp or jsp pages. For a windows application, the presentation tier consists of the windows forms or something else the GUI layer is able to display
Again, presentation logic tier may also implement services like caching. The page output caching for aspx pages is a good example
The GUI is something the user interacts with. In a web application, the GUI is the what the user sees in his browser. It is interesting to understand why the presentation tier is actually split up to presentation logic and GUI. This is because, in some cases, the presentation logic sits on the server, and the GUI layer will be on the client side. This is particularly true in case of a web application, where the asp or jsp pages are rendered and the resultant markup like html or wml is send to the client
A Different Approach - Model View Controller
A common approach is to split an application to various tiers, as we just discussed above. How ever, a different approach is to view the entire application as a Model, View and Controller (MVC architectural pattern). This is particularly true for web applications. In this section, I am not going to explain MVC inside out - the intention is just to convey the message that MVC is an alternate way of modelling the application
In MVC, the 'Model' represents the domain specific representation of data. MVC is not speaking much about how the data access is performed with in the model - and the model is expected to wrap the data access and object persistence.
The Presentation tier in MVC is split up to the 'View' and the 'Controller'. The View is expected to render the Model in a meaningfull way to the user. For example, in a web application, if you have a model which consists of a set of Employees, the logic in view may iterate through the employees and emit html code to display the list of employees in the browser. It is perfectly possible that more views may exist for one model. I.e, The same employee collection can be rendered as a bulleted list by another view
The Controller handles user actions and gestures, and responds to user events. For example, when a user clicks the 'new' button to add a new employee, the controller for that action is invoked. The controller will then make changes to the employee model. The view will then render the modified employee model to the display, so that user can view the new employee he added in the employee list
In some MVC implementations, the business logic is wrapped in the Model, and is perfectly possible. On the other hand, some developers may choose to implement business logic in the event handlers with in the controller. It is left to the developer to decide where he has to put the business logic.Few additional notes: In the Web Client Software factory released by the P&P group in Microsoft, they introduced a variant - Model View Presenter. But it seems that Microsoft is heading for a pure Model View Controller framework for ASP.NET. See Scott's blog entry regarding the same for some interesting reading.
The next step is to consolidate all your 'architecture thoughts', to form a high level wire frame (in your mind). Don't confuse this with high level design. Even before you begin the high level design, you should have answers for some of the basic questions. For example, here is a subset of questions you may end up asking yourself,
- What are the dependancies for tiers involved
- How the tiers are plumbed each other
- Where are the extension points?
- What all services I should use, and where?
- What are my persistence mechanisms?
Dependancies for the tiers involved
We discussed tiers involved in the system - but still we are not close enough to real world scenarios. For example, in the above definition of the business tier, we mentioned that the business tier is placed between data access tier and the presentation tier.
How ever, in a real world scenario, your business tier may be connected to some other sub systems - i.e, your business tier may be subscribing other services for rule processing. For example, if you are developing a flight ticket booking system, your business tier will be communicating with variuos web services for performing operations like querying flight timing, booking a ticket etc. In such scenarios, you may need to use facades to access your sub systems and to hide the complexity of your sub systems
Another common scenario is, you may need to separate the logic of finding the dependency between two layers from your actual implementation. For an example, assume that you need to invoke a web service to book the ticket from your application. The job of identifying "which" service to use, and "where" to locate the service, can be separated from your application, using techniques like dependency injection. Your application may know only "how" to communicate with the service. To give a simple scenario - you may consider injecting the dependency of the ticket booking web service to your business layer class.
There are various ways to inject dependency, one way is property based dependency injection. Your business layer class may have a property with type
ITicketBookingService. When this business layer class is instantiated, an instance of the proxy class (sure enough, this proxy class should implement your ITicketBookingService interface) of the ticket booking web service you need to use is created and assigned (injected) to this property. A detailed discussion of DI is out of scope of this article. Spring.NET framework provides excellent dependency injection capability. Also, Microsoft ObjectBuilder (which is used in factories like web client software factory) can be used for the same. (I still wonder why there is no separate 'dependency Injection Application Block' in Enterprise Library)
How the tiers are plumbed each other
Let us start with an example. Right now, you are developing a web application, and you have your presentation layer consuming your business layer classes directly. You simply create an object of your business layer class in the code behind of your aspx page, and call methods in your business layer object to pass data up and down. The development and QA is almost over, and you are waiting for the approval to move the project to production
One fine morning, you are finding a mail in your inbox from you technical manager - saying something like "Dude, let us move the application to production. But to maintain the load, we'll deploy the presentation tier in the web server, and the business logic in a separate app server." You are in a soup, because your architecture is not supporting distributed logic - and you simply can't deploy both these layers separately
Hence, as I mentioned earlier, it is imperative to consider factors like this during the initial stage. One common approach to build distribution of logic into the picture is bringing in a proxy tier, between the layers. For example, you may put a proxy tier between to business tier and presentation logic tier. The objective of the proxy tier is to expose the functionalities of one tier, so that the next tier can access it - to facilitate the need of distributed computing
Business Tier <--> Proxy Tier <-->Presentation Logic Tier
You may use protocls like SOAP, RMI, DCOM, CORBA etc for your proxy tier, based on the scenario. For example, if the presentation logic tier and business tier is developed in .NET, and both are expected to be deployed in the same LAN, you may go for .NET remoting. If the business logic needs to be exposed as services and needs to be accessed outside the enterprise domain by heterogenious clients, you may go for SOAP and so on.
When you use classic remoting technologies, like DCOM and RMI, the contract (methods and data types used by those methods) between two tiers is pre-defined. How ever, in Service Oriented systems, the client can dynamically discover the contract to use the same. For example, a web service will expose it's contract using web service discription language (WSDL), which provides efficient de coupling.
Some times, you may need to go for defining separate data contracts or data transfer objects (DTO) that can be send out and received back by the proxy tier. Then, internally, the proxy tier needs to convert the DTO objects to Business Objects, and vice versa. Though this conversion is an overhead, this make sure that the contract won't break even if your domain object model is changed
Where are the extension points?
Ideally, your application should have enough extension points where ever possible. For example, if you are communicating to three services for one single purpose, tomorrow you should be able to add a fourth service to your application with out any code change in the core framework.
A classic way of doing this is using provider pattern to define extensibility points. Using a provider based approach will help you to solve various scaling issues also in future. For example, assume that you are caching data in your web application. Normally, you use the default cache, which can cache data only in the current application domain. Tomorrow, if you are moving to a web farm scenario, where you have to use a shared/replicated cache between your web servers, you are in a soup. So, when you consider using a cache, you may consider using a provider model, so that you can change the cache provider at a later stage if required. Another example is, making certain parts of your system plugin based.
You may even go for a configuration file based provider model. From .NET 2.0 onwards, ASP.NET gives provider based extensibility, for various functionalities like membership management, role management etc. I've wrote an article long time back about creating a simple custom provider framework[^]. You may also need have a look at Microsoft Provider Toolkit here[^]
You may also be interested to see how Microsoft Enterprise Library uses the provider concept, for providing configuration file driven features.
Deciding where you have the extension points during the initial stage itself is the key to ensure extensibility.
What all services I should use, and where?
Various services like caching, logging, security (authentication and authorization) etc needs to be used in multiple tiers. This decision is pretty critical, mainly to ensure that various non functional requirements are met properly. As the stake holders might not be even aware about the overhead required to implement such services, it is pretty important to communicate the same to the stake holders during the initial phase itself.
The key is re-usability. Most of these services are common to projects. Hence, If you have an organization level framework which consists of these services - it is going to reduce the overhead in a big way.
What are the persistence mechanisms?
Persisting the data and querying it back is a major consideration. Some people prefer using ORM (Object Relational Mapping) frameworks like Hibernate or NHibernate(the .NET port of hibernate) - http://www.nhibernate.org/[^] - others may follow the classic way of writing stored procedures and then using data tier classes to consume them.
Another approach is to generate strongly typed classes based on the data base tables, using some code generation technique. Microsoft Web Service software factory provides few functionalities like this. Also, there are other code generators or meta coding frameworks - I've used My Generation[^] earlier in a couple of projects.
We just discussed some driving forces, approaches and considerations involved in architecting a new system. This is in no way a complete list - the whole objective is to help you analyze the thought process involved in architecting a solution.