Let's share Java based web application architectures!
There are lots of different architectures for web applications which are to be implemented using Java. The answers to this question may serve as a library of various web application designs with their pros and cons. While I realize that the answers will be subjective, let's try to be as objective as we can and motivate the pros and cons we list.
Use the detail level you prefer for describing your architecture. For your answer to be of any value you'll at least have to describe the major technologies and ideas used in the architecture you describe. And last but not least, when should we use your architecture?
I'll start...
Overview of the architecture
We use a 3-tier architecture based on open standards from Sun like Java EE, Java Persistence API, Servlet and Java Server Pages.
- Persistence
- Business
- Presentation
The possible communication flows between the layers are represented by:
Persistence <-> Business <-> Presentation
Which for example means that the presentation layer never calls or performs persistence operations, it always does it through the business layer. This architecture is meant to fulfill the demands of a high availability web application.
Persistence
Performs create, read, update and delete (CRUD) persistence operations. In our case we are using (Java Persistence API) JPA and we currently use Hibernate as our persistence provider and use its EntityManager.
This layer is divided into multiple classes, where each class deals with a certain type of entities (i.e. entities related to a shopping cart might get handled by a single persistence class) and is used by one and only one manager.
In addition this layer also stores JPA entities which are things like Account
, ShoppingCart
etc.
Business
All logic which is tied to the web application functionality is located in this layer. This functionality could be initiating a money transfer for a customer who wants to pay for a product on-line using her/his credit card. It could just as well be creating a new user, deleting a user or calculating the outcome of a battle in a web based game.
This layer is divided into multiple classes and each of these classes is annotated with @Stateless
to become a Stateless Session Bean (SLSB). Each SLSB is called a manager and for instance a manager could be a class annotated as mentioned called AccountManager
.
When AccountManager
needs to perform CRUD operations it makes the appropriate calls to an instance of AccountManagerPersistence
, which is a class in the persistence layer. A rough sketch of two methods in AccountManager
could be:
...
public void makeExpiredAccountsInactive() {
AccountManagerPersistence amp = new AccountManagerPersistence(...)
// Calls persistence layer
List<Account> expiredAccounts = amp.getAllExpiredAccounts();
for(Account account : expiredAccounts) {
this.makeAccountInactive(account)
}
}
public void makeAccountInactive(Account account) {
AccountManagerPersistence amp = new AccountManagerPersistence(...)
account.deactivate();
amp.storeUpdatedAccount(account); // Calls persistence layer
}
We use container manager transactions so we don't have to do transaction demarcation our self's. What basically happens under the hood is we initiate a transaction when entering the SLSB method and commit it (or rollback it) immediately before exiting the method. It's an example of convention over configuration, but we haven't had a need for anything but the default, Required, yet.
Here is how The Java EE 5 Tutorial from Sun explains the Required transaction attribute for Enterprise JavaBeans (EJB's):
If the client is running within a transaction and invokes the enterprise bean’s method, the method executes within the client’s transaction. If the client is not associated with a transaction, the container starts a new transaction before running the method.
The Required attribute is the implicit transaction attribute for all enterprise bean methods running with container-managed transaction demarcation. You typically do not set the Required attribute unless you need to override another transaction attribute. Because transaction attributes are declarative, you can easily change them later.
Presentation
Our presentation layer is in charge of... presentation! It's responsible for the user interface and shows information to the user by building HTML pages and receiving user input through GET and POST requests. We are currently using the old Servlet's + Java Server Pages (JSP) combination.
The layer calls methods in managers of the business layer to perform operations requested by the user and to receive information to show in the web page. Sometimes the information received from the business layer are less complex types as String
's and int
egers, and at other times JPA entities.
Pros and cons with the architecture
Pros
- Having everything related to a specific way of doing persistence in this layer only means we can swap from using JPA into something else, without having to re-write anything in the business layer.
- It's easy for us to swap our presentation layer into something else, and it's likely that we will if we find something better.
- Letting the EJB container manage transaction boundaries is nice.
- Using Servlet's + JPA is easy (to begin with) and the technologies are widely used and implemented in lots of servers.
- Using Java EE is supposed to make it easier for us to create a high availability system with load balancing and fail over. Both of which we feel that we must have.
Cons
- Using JPA you may store often used queries as named queries by using the
@NamedQuery
annotation on the JPA entity class. If you have as much as possible related to persistence in the persistence classes, as in our architecture, this will spread out the locations where you may find queries to include the JPA entities as well. It will be harder to overview persistence operations and thus harder to maintain. - We have JPA entities as part of our persistence layer. But
Account
andShoppingCart
, aren't they really business objects? It is done this way as you have to touch these classes and turn them into entities which JPA knows how to handle. - The JPA entities, which are also our business objects, are created like Data Transfer Objects (DTO's), also known as Value Objects (VO's). This results in an anemic domain model as the business objects have no logic of their own except accessor methods. All logic is done by our managers in the business layer, which results in a more procedural programming style. It's not good object oriented design, but maybe that's not a problem? (After all object orientation isn't the only programming paradigm which has delivered results.)
- Using EJB and Java EE introduces a bit of complexity. And we can't use purely Tomcat (adding an EJB micro-container isn't purely Tomcat).
- There are lots of issues with using Servlet's + JPA. Use Google for more information about these issues.
- As the transactions are closed when exiting the business layer we can't load any information from JPA entities which is configured to be loaded from the database when it's needed (using
fetch=FetchType.LAZY
) from inside the presentation layer. It will trigger an exception. Before returning an entity containing these kinds of fields we have to be sure to call the relevant getter's. Another option is to use Java Persistence Query Language (JPQL) and do aFETCH JOIN
. However both of these options are a little bit cumbersome.