J2EE Project Structures
I have always been a huge proponent for separation of concerns, code reusability, and code independence such that you can easily move code around, replace, re-use, etc. Even though often times you sacrifice speed due to additional layers of indirection, you gain much in maintainability. Maintainability is often an overlooked statistic as it is harder to measure than just time. However, as versions are released, use cases evolve, and technology stacks change, that maintenance nightmare can start to build quickly. Well balanced project structures can endure very well over time, far more than quickly thrown together projects. Even though a quick thrown together project can be shipped sooner, in the end it will fail to release version updates as fast and will result in a higher lifecycle cost (due to bugs, maintenance issues, etc). Anyways, enough with the project management side. What I really wanted to share was my ideal project structure that I am proposing to myself to use for future projects.
First off, I typically use Eclipse and Eclipse projects to manage my development systems. Nonetheless, the IDE does not really matter, nor does the build system in whether you use Maven with separate projects or Ant with separate build files. The following project structure is based deeply in separation of concerns and each project is expected to live on its own with minimal dependencies to each other. This allow the project to be easily re-used elsewhere. However, before we can begin to define the structure we must first define the libraries and agreed upon systems that will compose everything. This is needed so that each layer can have a set of expectations and similar patterns to re-use. Remember, re-use is not just about code, but also about patterns. So, let’s define the shared systems and libraries.
As a J2EE project, I believe both in a web-independent portion and a web-dependent portion. In other words, I want shared libraries for the entire system to be web agnostic (so that you can easily move to a different UI medium such as thick client apps). However, I also support many of the J2EE features such as AOP (aspect-oriented programming) and DI (dependency injection) provided by Spring, EJB 3.0, WebBeans, etc. However, even though those features are part of web-based libraries, the libraries themselves can live outside of a web application. So, for my intents and purposes, I plan to use AOP and DI heavily through all layers of the application. This promotes two huge advantages. First, classes are implemented and designed to interfaces by having the implementations auto-injected. Second, it provides a methodology to reduce dependencies and complexity by classes not having to manage (ie: retrieve) or create a particular class. For current projects I tend to utilize Spring to provide this framework (although a J2EE application server with EJB 3.0 support works just as well). For future projects, I plan to use the upcoming Java standard for Web Beans (again, ignore the “web” notation as it is really web-independent).
Now that we have a common set of libraries for AOP and DI, let’s define the structure. The way I like to define structures is by starting at the broadest level to define the generic layers. Then, within each layer, I do the same thing, until I have a complete structure. For a J2EE based project, we have the following high level layers: model, service, and user interface. If you think in terms of MVC, the M is the model, the V is the user interface, and the C is the service. MVC can, and should, be represented at multiple portions of our project architecture (from the high level overview just presented to the actual UI implementation such as JSF or struts). The model is really just simple POJO classes and really does not need to be broken down further. The service layer is composed of the base service and the service implementations. This can include other libraries such as JPA/Hibernate. Finally, the user interface is broken down into a shared UI framework, UI implementation (JSF, struts, etc), and the actual UI pages or views.
The project hiearchy aside, I envision the following projects in order to separate each layer and emphasize independence, reusability, and separation of concerns.
Model
The model is the base set of interfaces that the entire system will use. The model defines the use cases, requirements, and interactions needed for a given project. Note that the model should only consist of the classes necessary to describe the project data and should not include any type of controller classes or interfaces used to perform a job or activity on the model. The model should only consist of interfaces so as to promote coding to interfaces. Further, by using interfaces, you allow the implementation to change without touching the model or any other dependencies for that matter. For example, if you start with JPA, you would implement the interfaces using annotations. If you then switched to Web Services, you would not want those JPA annotations. By separating the interfaces in the model (being annotation-less) from the implementation, you allow the implementation to do as it pleases, so long as it implements the contract of the interface. Further, I do not believe in making the model interfaces serializable or extend serializable. I believe serialization is a detail of the implementation and the implementation should decide if it supports serialization. For example, what if the implementation I am using cannot support serialization for whatever reason (ie: a 3rd party library is not serializable). If the interfaces required serialization, then that implementation will fail to work according to the contract. In other words, the contract of the model interfaces should be agnostic of any technology and be purely POJOs.
Default (Shared) Model [optional]
The default model is an optional project/component that defines a generic implementation according to the contract of the model. If you expect multiple implementations of the model within your system, this may be an ideal project as it defines the base POJO implementation. In other words, it defines the base getters/setters and instance variables. The only dependency this project should have is on the model. Again, this project should be agnostic to any technology (aside from the agreed upon systems [ie: AOP/DI]) and not define serialization requirements.
Services
Services provide the activities and interactions of the model data and generally provide a CRUD (create-retrieve-update-delete) system. This project only defines the interfaces of the services and activities and has no implementation-specific classes. It only depends on the model and knows about no model implementation. The service interfaces define the contracts that implementations implement in order to provide the necessary functionality. The interfaces should expose all use cases and activities required by the system. Note that the activities should be the actual high level activities and not UI-based activities. The services are expected to be re-used in any type of project, whether thick client, web client, etc. A new project should be able to take the services and models and re-use them regardless of the end technology. If your system has distinctly different types of services, then each type should live in its own project and only depend on each other when absolutely necessary. Always remember to separate concerns as much as possible and leave dependencies to a minimum. The less dependencies, the less complex; the less complex, the less bugs, especially regressions from changes.
Service Implementation
A service implementation provides an implementation for both the model and service interfaces. It uses the same technology for both contracts. For example, JPA could be used as the service implementation. In that case, a set of model implementations would be created with the appropriate JPA annotations. The service implementations would use DI to inject an entity manager in order to execute queries to perform the necessary actions. Later on, web services or RMI could be used to provide the service layer by annotating the services with the necessary annotations to expose web services. In either case, the actual system does not change as the system codes to the service interface allowing the implementations to be swapped in and out as necessary. As such, the service implementation should only depend on the model and service interface projects. Similar to multiple service projects per distinct type, the implementation should also be a 1-to1 mapping of interface to implementation. In such a case, it may be desirable to have a separate project for the model implementation. Also, in cases with multiple service implementations, it is ideal to have a shared project of utility classes, base classes, etc.
UI Framework
The UI framework defines a set of classes or interfaces that interact with the model and service interfaces (NOTE: interfaces only, never implementations). The code should be platform independent and service independent. In other words, the actual UI implementation should not be known (whether thick client, web client, jsf, struts, etc). The UI framework is the base class for exposing the UI logic. Whereas the services are UI-independent activities, the UI framework exposes the UI-dependent activities and implements them via the model and services. Often times, the UI framework implements task-based logic or conversational-based logic used to perform specific tasks within a UI. As such, the only dependencies the framework should have is on the model and service interfaces.
UI Implementation
The UI implementation uses the UI framework with a particular UI technology stack (jsf, struts, seam, swing, etc). It implements the controllers, events, actions, listeners, and handlers necessary to implement the UI framework interfaces and the UI. It acts as the glue or control between the UI framework and the UI stack. Thinking of it as an MVC system, the model, services, and UI framework projects are the Model, the UI pages are the View, and this project is the Controller between the model and the view. As this project only depends on the UI framework, it also only depends on the model and service interfaces. Again, no implementation-specific (aside from UI-specific) libraries are being used. This allows transparent swapping to the UI implementation.
Web Application
The web application is the glue that puts it all together. It includes the UI pages, configuration files, and ties all the dependencies together. This project depends on everything required to run the application. It selects one or more service implementations that should be used as the implementation within the UI. The configuration files provide the glue for the AOP and DI configuration as necessary. The web application is composed of the specific UI language such as JSF, struts, Seam, etc. Selecting a new library or medium (ie: going from web application to thick client Swing application) just requires coding the UI to that library and using the existing service tier. No other code changes are required.
Example
As an example, let’s go through the steps to create a simple hotel booking application. This example does not provide actual code and only presents a high level overview to get a glimpse of the power and advantages to structuring a project in this manner.
First, we need to itemize the use cases and requirements:
- Search for hotel by city and state
- View list of hotels within city and state
- View hotel details (room types, ameneties, etc)
- Check room rates and availability
- Book room and create reservation
- Purchase room
- Confirm booking
From this short list of simple use cases, let’s create some model classes as part of the model project:
- Hotel : id, name, description, list of amenities, list of rooms
- HotelRoom : id, hotel association, type, name, description
- Reservation : hotel, room, date period, confirmation number
With the model, we can now create the service level tier project defining the use case activities:
- Hotel Service
- - getHotelsByCityState(city, state)
- - getHotelById(id)
- Booking Service
- - getHotelRates(hotel, fromDate, toDate)
- - bookReservation(hotel, hotelRoom, fromDate, toDate)
- Purchase Service
- - purchaseReservation(reservation)
Despite its simplicity, you can see that from the model and the service tier, there is nothing library dependent. The code is complete POJOs based on pure use case scenarios. With these simple interfaces, we can now select an implementation. For our example, we will use JPA/Hibernate to provide the functionality. Via JPA, we will annotate the model implementation classes with JPA entities and columns to map the data to a backend database. For the service tier, we will use JPA queries and annotations to specify the interaction within JPA to retrieve the data. Using DI, we can inject the entity manager interface at runtime into each service implementation. Each implementation can also use transactional annotations to control SQL transactions. Now think about how simple it would be to change from Hibernate to using web services or a SOA (service oriented architecture).
With the service implementation in place, let’s define the UI framework project. Remember that the framework is typically conversational based or task-oriented. Whereas the services deal with specific tasks (find a hotel, book a room, etc), the UI uses more user-centric tasks (view hotel from a city, purchase and confirm a hotel room). These tasks generally involve one or more service invocations. Sometimes the tasks are similar or overlap and sometimes the user-centric tasks involve multiple separate use case tasks. For our hotel example, we can see the following tasks:
- searchForHotels(city, state)
- viewHotelDetails(hotel)
- viewHotelAvailability(hotel, fromDate, toDate)
- bookHotelReservation(hotel, fromDate, toDate)
Rather than have the framework classes have to instantiate a particular service, we can let web beans or any other DI-based library inject the necessary services or resources into the class. As a result, the UI framework only knows about the service interfaces (coding to interfaces) and has no knowledge or concern over whether it is JPA or web services or any other implementation. For example, the booking system could be structured such as:
BookingController
- @In BookingService bookingService
- @In PurchaseService purchaseService
- @Out @Transactional Reservation bookHotelReservation()
- if (purchaseService.purchaseRoom())
- bookingService.bookHotelRoom()
This code does not have any type of dependencies except for the service. Since the agreed upon system is web beans (this could also be Spring or EJB 3.0), we can use annotations to cause the container to inject the actual implementation. That means that we can just as easily swap out the JPA/Hibernate implementation of the service tier for a web-service tier. Further, we could mix and match the implementations and use annotations to further clarify specific requirements. The separation of concerns creates an easily maintainable app that still scales.
The only remaining piece is the actual UI application. For our intents and purposes, let’s use JSF. Since JSF already ties into web beans via EL, we can easily expose the UI templates to the booking controller. However, as the controller does not have any error handling, let’s create a JSF specific controller and inject the UI framework controller into it.
@Controller class JsfBookingController
- @In BookingController bookingController
- String bookRoom()
- if (!bookingController.bookHotelReservation()) {
- FacesContext.getCurrentInstance().addErrorMessage(…);
- return “error”;
- } else {
- return “confirmation”;
- }
Again leveraging the separation of concerns, we can just as easily modify the booking controller implementation or provide another implementation without affecting any other layers. Further, we can modify that implemetation to add/remove code and as long as it agrees to its contract, the UI does not need to change. Now, just for fun, let’s imagine we wanted a thick client app as well. The only difference is that rather than a JSF controller and JSF template, we have a Swing application dialog and Swing controller. In the end they both invoke the same backend method. The only difference is in how they report errors, retrieve data from the UI, etc. This allows the shared functionality to be provided in the UI framework in a truly shared manner while the UI-specific functionality (reporting errors, retrieving UI data, etc) remains in the UI implementation.
In conclusion, this methodology requires many more classes, projects, segregation, etc, but it also allows the application to be maintainable, shared, and dependency free. Further, it allows developers to work on specific areas of the code targetted at their specific strengths. For example, an architect could take use cases and create the model/service interfaces, while a JPA expert could work on the implementation of those interfaces specific to JPA, while a UI expert works on the shared UI framework, while a JSF expert creates the ties within JSF to the UI. These groups can co-exist and work mostly in parallel without having to wait for each other as each group codes to a given set of interfaces. Later on, when technology changes or new UI mediums exist (ie: iPhone-specific interface), the application can quickly evolve without requiring a complete rewrite or horrendous spaghetti code.
Anyways, this guide is merely my recommendation on layering J2EE projects. There are many other design patterns, ideas, and methodologies. I leave you with one last thought. Rather than saying this is the correct solution or that is a better solution, always stay open minded and evaluate all the given options seeing which one fits best with your given project lifecycle, timeframe, and use cases.
