Designing the Business Tier

At this point in our application design discussion, we have a way of representing the data in our problem domain so that we can manipulate it in code, and we have a way of storing this data in a database and then getting it back out later on. However, currently we are not doing much with this data. Unless your application is especially simplistic, chances are, some kind of logic is going to need implementing. Earlier on, we discussed cases where you should encapsulate logic inside your Domain Objects. In this section, we look at providing a layer of service objects to provide a standard interface to the rest of your application logic.

Why Have a Business Tier?

As with the question as to why you should have a Data Access Tier, the answer to this question is plain and simple once you have implemented a few applications without one. If you do not bring together all business logic in a single place, it ends up spread out through your presentation code, typically resulting in lots of code duplication, not to mention creating code that lacks clearly-defined boundaries for responsibilities. Code duplication issues aside, failure to define clear boundaries between code with different responsibilities often results in code that is difficult to debug, because it becomes hard to pinpoint the location of a given function.

A well-defined business layer acts as a sort of gateway into your application, providing your presentation code with a simple, unified way to get at business logic. A good business layer also serves as a definition of what your application can actually perform and what logic is available to be presented to the user.

A significant drawback of not having a Business Tier comes about when you decide to have two kinds of user interface for the same logic. Perhaps you built a web application, but now you want to provide a desktop-based application for users who use the application often. If your business logic code is intertwined with your web presentation code, you are either going to have to refactor the code out of the Presentation Tier, which requires a significant amount of effort in rework and testing, or simply reproduce the business logic code again, this time intertwined with your Swing or SWT code.

We cannot emphasize enough the importance of building a solid Business Tier. Without one, you are setting up your project for failure. Inevitably, the inability to access business logic centrally within an application leads to bugs, maintenance headaches, and unhappy customers.

Designing Business Interfaces

As with most components in your application, you should start by defining a set of interfaces for the service objects in your application. Any code that interacts with your Business Tier should do so through these interfaces. For components that Spring manages, you can supply implementations using DI. If you have to support components Spring does not manage, you may want to supply a simple Factory class to allow for implementation lookup.

Business Tier Dependencies

As with all the interfaces we have talked about, you should avoid defining dependencies in the interfaces of your service objects. The service object should be completely implementation- independent. A well-defined service object interface only has methods that serve business functions. Avoid exposing types from your Data Access Tier through your service objects. Your service objects should insulate the presentation code from the underlying Data Access Tier completely. What is to say that you will be using DAOs anyway? One of your service objects might access all its data using web services or via a JMS queue. A good way to ensure that your service object interface is as accessible as possible is to ensure that return and argument types do not couple the presentation code to anything other than the DOM (and of course Java types!). You are going to be passing Domain Objects through all the tiers of your application, but other components such as DAOs should stay well within their own tiers.

Using Abstract Base Implementations

When building a service object implementation, we always find it useful to create an abstract base class to store all the dependencies. This has two benefits: first, we don't need to define all the dependencies again if all we are doing is changing the implementation; and second, the final implementation class is fully focused on business logic, without the clutter of a bunch of setters for dependency injection. For instance, in the SpringBlog application, we create the AbstractDaoBasedBlogManager class to hold all the DAO dependencies (see Listing 11-17).

Listing 11-17: The AbstractDaoBasedBlogManager Class

image from book
package com.apress.prospring.business;      import com.apress.prospring.data.AttachmentDao; import com.apress.prospring.data.CommentDao; import com.apress.prospring.data.EntryDao; import com.apress.prospring.data.UserDao;      public abstract class AbstractDaoBasedBlogManager implements BlogManager {          protected UserDao userDao;          protected EntryDao entryDao;          protected AttachmentDao attachmentDao;          protected CommentDao commentDao;          public void setUserDao(UserDao dao) {         this.userDao = dao;     }          public void setEntryDao(EntryDao dao) {         this.entryDao = dao;     }          public void setAttachmentDao(AttachmentDao dao) {         this.attachmentDao = dao;     }          public void setCommentDao(CommentDao dao) {         this.commentDao = dao;     } }
image from book

Here you can see that we defined four properties for the DAOs needed by any BlogManager implementation that accesses blog data from the database. If we had a BlogManager that accessed data from XML, we could have defined AbstractXmlBlogManager or AbstractWebServiceBlogManager for a BlogManager that accessed data using web services.

When doing this, only define the dependencies that you know are going to be common to all implementations. It is clear that, if we are creating a base class for all DAO-based BlogManager implementations, then the DAOs are going to be dependencies. When you look at the actual implementation of BlogManager we created, you will see that we actually define another dependency there, one that may not be relevant to all DAO-based implementations.

Service Object Granularity

Something that many developers find quite hard at first, but that is actually quite easy once you are familiar with it, is creating service objects with the correct granularity. The first thing I always tell my developers is that there really is no correct level of granularity for service objects. Many different levels of granularity work just as well as each other. However, I also tell my developers that there is a preferred level of granularity, and this is the level that makes the service objects simple to use and maintain.

For a small application like SpringBlog, a single service object is usually all you need; anything else is overkill and adds unnecessary complexity. In this case, we define application size by the number of business functions an application must perform and the number of logical groups these functions fall into. When you have a larger application with many functions or function groups, this is the time to split these into separate service objects.

Splitting your business functions into different service objects is usually a fairly intuitive process. We start by looking at the collective set of functions and then grouping them into logical sets. For instance, we might have ordering functions, product catalogue functions, and account management functions. From this, we can then create the service object interfaces: OrderManager, ProductManager, and AccountManager in this example. Then we simply define each function in the appropriate service object.

Remember, there is really no right and wrong answer when you are designing service object interfaces. What you are looking for is a set of easy-to-work-with interfaces. If you are going to be writing a lot of code to interact with your business logic layer, you want your service objects to be logically ordered and simple to use.

The DefaultBlogManager Implementation

The SpringBlog doesn't really have too much business logic. Much of the code in the Business Tier is simply acting as a middleman between the presentation code and the Data Access Tier. However, some bits of logic, such as the login code and the auditing code, are of interest.

Note 

Although we have implemented a simple login() method in the DefaultBlogManager class, that is as far as we take any concept of security or user identity in this example application. We did not want to muddy the waters with security issues when we look at the web front end later on. As we write this, an interesting security framework designed specifically for Spring, Acegi, is currently in development. You can check out the website at http://acegisecurity.sourceforge.net/ to see how this project is progressing. Hopefully, this framework will be released by the time the second edition comes around so that we can cover it in some detail.

Listing 11-18 shows the full code for the DefaultBlogManager implementation.

Listing 11-18: The DefaultBlogManager Class

image from book
package com.apress.prospring.business;      import java.util.Date; import java.util.List;      import org.springframework.beans.factory.InitializingBean;      import com.apress.prospring.domain.Attachment; import com.apress.prospring.domain.Comment; import com.apress.prospring.domain.Entry; import com.apress.prospring.domain.User;      public class DefaultBlogManager extends AbstractDaoBasedBlogManager implements         InitializingBean {          private AuditService auditService;          public void afterPropertiesSet() throws Exception {         if (auditService == null) {             auditService = new DefaultAuditService();         }     }          public void setAuditService(AuditService auditService) {         this.auditService = auditService;     }          public User login(String username, String password)             throws InvalidCredentialsException {         User user = userDao.getByUsernameAndPassword(username, password);              if (user == null) {             throw new InvalidCredentialsException(       "The credentials you supplied do not match a known user profile. Try again");         } else {             return user;         }     }          public List getMostRecentEntries() {         return entryDao.getMostRecent(100);     }          public List getAllEntries() {         return entryDao.getAll();     }          public Entry getEntry(int entryId) {         return entryDao.getById(entryId);     }          public void deleteEntry(int entryId) {         entryDao.delete(entryId);         auditService.writeAuditMessage("Entry Id " + entryId + " deleted.",                 User.ROOT);     }          public void saveEntry(Entry entry) {              if (entry.getEntryId() <= 0) {             // new entry - make sure              // post date is set             entry.setPostDate(new Date());         }              entryDao.save(entry);         auditService.writeAuditMessage("Entry " + entry + " saved", User.ROOT);     }          public void saveComment(Comment comment, User postingUser) {         comment.setPostedBy(postingUser);              if (comment.getCommentId() <= 0) {             // this is a new comment             comment.setPostDate(new Date());         }              commentDao.save(comment);         auditService.writeAuditMessage("Comment " + comment + " saved.",                 postingUser);     }          public List getEntryAttachments(int entryId) {         return attachmentDao.getByEntry(entryId);     }          public void attachToEntry(Attachment attachment, int entryId) {         attachmentDao.insertEntryAttachment(attachment, entryId);     }          public void attachToComment(Attachment attachment, int commentId) {         attachmentDao.insertCommentAttachment(attachment, commentId);     }          public Comment getComment(int commentId) {         return commentDao.getById(commentId);     }          public void deleteComment(int commentId) {         commentDao.delete(commentId);         auditService.writeAuditMessage("Comment: " + commentId + " deleted.",                 User.ROOT);     }          public List getAllUsers() {         return userDao.getAll();     } }
image from book

This code shouldn't really need explaining—most of the logic is fairly basic—but there are three things of interest here. First, notice that in some of the methods, such as saveComment() and saveEntry(), we use an AuditService object to create an audit of actions being performed in the database. Originally, we had a private audit() method in the DefaultBlogManager class, but this played havoc with testing, so we refactored it into a new service object. We talk about the reasons behind this decision in greater detail in the next section.

Second, notice that we throw a checked exception for the login() method when a user provides invalid credentials. The reason is simple—this is a recoverable error. In a rich client application, the exception can be caught and the login dialog can be shown again. For a web application, the exception is caught and the user is redirected back to the login page. In this case, using a checked exception is valid and desirable, because it serves as a gentle nudge to code in the Presentation Tier to handle invalid logins correctly.

Finally, you may notice the distinct lack of validation code in this example. We have not forgotten about validation code; it just sits elsewhere in our application. We discuss validation is more detail later in the chapter.

Services Used by Service Objects

It is not uncommon for a service object to require some services itself. The question is whether you should create separate service objects for these services or not. Clearly, if more than one of your service objects needs the same shared service, then you should create another service, but what if only one service object needs the service?

Consider the case of the DefaultBlogManager class in SpringBlog. This class needs to log audit messages whenever an action is taken that modifies the comment or entry data in the database. Originally we had this logic encapsulated in the DefaultBlogManager.audit() method and we used an implementation of AuditDao to store the audit messages in the database. At face value, this approach was fine, but it did cause us some problems in testing. The problem we faced was reliably testing that DefaultBlogManager did in fact invoke audit() when it was supposed to.

The simplest way to do this is to count the number of audit records in the database before invoking a method on DefaultBlogManager and then count them after, checking to see that the number has increased. This is an okay solution, but a better solution would not require database access at all. Adding too many tests that need to query the database really slows down the test run, and this leads to situations where developers stop running tests because they take too long. We overcame this problem by refactoring the audit code into a separate service object behind the AuditService interface shown in Listing 11-19.

Listing 11-19: The AuditService Interface

image from book
package com.apress.prospring.business;      import java.util.Date;      import com.apress.prospring.domain.User;      public interface AuditService {          public void writeAuditMessage(String data, User user);     public void purgeAudit(Date oldestDate); }
image from book

The default implementation of this interface simply uses AuditDao to store the data in the database. Where this interface becomes useful is in testing. To test that the DefaultBlogManager class is correctly invoking the audit service, we can create a mock implementation of AuditService that counts calls to writeAuditMessage(). In this way, we can accurately determine whether or not the AuditService has been invoked without having to query the database. Because we are not relying on the database at all, we can also pass mock implementations of EntryDao and CommentDao to DefaultBlogManager when testing whether we can reduce the runtime of the tests even more. Listing 11-20 shows the JUnit test that we created to test audit usage.

Listing 11-20: Testing AuditService Usage

image from book
package com.apress.prospring.business;      import java.util.Date; import java.util.List;      import junit.framework.TestCase;      import com.apress.prospring.data.CommentDao; import com.apress.prospring.data.EntryDao; import com.apress.prospring.domain.Comment; import com.apress.prospring.domain.Entry; import com.apress.prospring.domain.User;      public class AuditInvokedTest extends TestCase {          private DefaultBlogManager bm = new DefaultBlogManager();          private CommentDao commentDao = new MockCommentDao();          private EntryDao entryDao = new MockEntryDao();          private MockAuditService auditService;          public AuditInvokedTest() {         bm.setCommentDao(commentDao);         bm.setEntryDao(entryDao);     }          public void setUp() {         auditService = new MockAuditService();         bm.setAuditService(auditService);     }          public void testSaveEntry() {         bm.saveEntry(new Entry());         performAssert();     }          public void testSaveComment() {         bm.saveComment(new Comment(), new User());         performAssert();     }          private void performAssert() {         assertEquals("The Audit Service was not invoked",                                        1, auditService.callCount);     }          private class MockAuditService implements AuditService {         private int callCount = 0;                  public void writeAuditMessage(String data, User user) {             callCount++;         }              public void purgeAudit(Date oldestDate) {                      }                  public int getCallCount() {             return callCount;         }     }          /* MockCommentDao and MockEntryDao omitted for clarity */      } 
image from book

By using a testing strategy like this, we make it so that we are not dependent on the database to perform a test that has little to do with the database, plus we are keeping test runtimes down, thus reducing the chance that developers will just stop running them. When building any part of your application, be on the lookout for chances to refactor that will improve your ability to test the application. Even if you already have a test for a particular case, you may be able to remove a dependency on the database or on another expensive-to-use component by refactoring parts of your application.

Assembling Domain Object Relationships in the Business Tier

A common practice that we see in many applications is to assemble Domain Object relationships in the Business Tier. This isn't necessarily a bad practice, but you should think about how you are defining the responsibilities of your components. If you find yourself constantly making multiple DAO calls to assemble the same Domain Object relationships in your Business Tier, then this is really a data loading issue and should be moved into the Data Access Tier. However, in some cases, you may need to assemble a large number of Domain Objects into a complex relationship, perhaps to support a reporting or data extraction process, and then this might be more suitably performed in the Business Tier.

When deciding where to assemble Domain Object relationships, I usually ask myself two questions: "Is this relationship typical of how the data is accessed throughout the application?" and "Am I assembling this relationship to support some special business process?" Most relationships, such as the Order and OrderLine relationship, are typical to how those Domain Objects are used in the application; these relationships should be assembled in the Data Access Tier. Other relationships, such as relating Order, OrderLine, User, and Product to support a reporting process, are not typical of the way Order data or Product data is usually loaded from the database, so you would be better off assembling this relationship in the Business Tier.

Business Method Granularity

When designing service interfaces, pay attention to the granularity of your methods. Remember that the responsibility of a service interface is to expose the business functions your application performs to presentation code; the natural level of granularity for methods in a service interface is one per business operation. Sometimes you will have operations that you can invoke individually or as part of a larger operation. In this case, create methods for the smaller and larger operations, and have the larger operation invoke the smaller operation as required.

We often think that many teams spend far too much time worrying about business method granularity. We find that one method per logical business operation provides the simplest and most flexible interface into the business logic of our application.

Transaction Boundaries

One factor that does place additional considerations on the granularity of your business methods is transaction demarcation. If you plan to use the declarative transaction management functions in Spring, then you will be controlling the applicability of transactions at the method level. Although you don't have to apply transactions to your service objects—you can do this at the DAO level—we find it better to mark transactions in the Business Tier; that way, we can integrate as many calls to as many DAOs as we want into a single transaction.

Using the declarative transactions functions in Spring, the methods of your business interfaces serve as boundaries for your transactions. For each method, you can tell Spring that it must run in a transaction context. If you have methods that can be called individually or as part of larger transactions, then you can have Spring enlist these methods in existing transactions or create new ones when a transaction does not exist. Remember, though, for this to work, the method associated with the smaller operation must be invoked in the control flow of the method that started the transaction. This means that you cannot invoke methods foo() and bar() from your presentation code and expect them to share the same transaction (unless you mark the transaction in the presentation code, which isn't recommended). Chapter 12 presents a fuller discussion of the issues surrounding transaction management.

Implementing Validation Logic

A distinct part of the business logic of any application is the logic used to validate the data provided by the application's users. After all, users are only human, and inevitably, one of them will make a mistake and enter invalid data. If you fail to validate data as it enters your system, you are left with bad data floating around, poised to wreak havoc with your finely crafted business logic. The common problem we see with validation logic is that many teams just do not bother, mainly because writing it is so darn repetitive. However, it is the lesser of two evils compared to late nights spent debugging your application due to bad data, and thankfully, many utilities are now available to ease the burden of creating validation logic.

In this section, we explore the validation support in Spring and some of features offered by the Commons Validator, and we take a quick peek at what features will be available validation- wise in the next release of Spring.

Where to Call Validation?

Before we start discussing validation support, we should first look at where in your application you should call your validation code. You may have noticed that we did not call any validation code in our DefaultBlogManager, and you may be wondering why. The reason for this is simple—we are going to have the Spring MVC framework invoke our validation logic for us. You will almost always want to perform some kind of validation in the Presentation Tier (remember that one of the responsibilities of the Presentation Tier is to process user input) so that you can handle bad data directly and show some kind of error message. If you are only going to have a single front end for your application then you should invoke your validation logic here; the closer your validation logic is to the presentation code, the easier it is to provide useful feedback to the user.

For applications that have multiple front ends, you will still want to invoke the validation logic from the presentation code of each front end, but you may also want to call it from your service objects as well, to serve as a sort of second line of defense against any misbehaved user interfaces.

Whichever approach you choose, always factor your validation logic into a distinct set of objects. Avoid being tempted to interweave validation code with your presentation code, because this makes adding additional user interfaces very difficult and error prone, plus you will lose out on the reusability that a centralized set of validation rules gives you.

Spring Support for Validation

The current version of Spring provides framework-level support for validation in the form of the Validator interface. When working with Spring, all your validation logic should be encapsulated in objects that implement the Validator interface, which is shown in Listing 11-21.

Listing 11-21: The Spring Validator Interface

image from book
package org.springframework.validation;      public interface Validator {          boolean supports(Class clazz);          void validate(Object obj, Errors errors);      }
image from book

Your Validator implementations indicate which classes they are capable of validating using the supports() method, and then they perform the actual validation in the validate() method. Your code uses the Errors object passed to validate()to define errors that are present in the object in question. The Spring framework then uses this data to render error messages in the Presentation Tier. Currently, only the Spring MVC framework has built-in support for the Validator interface, but in the future, we expect that the same interface will be used for validation in the Spring Rich Client Platform. We are not going to look at how Validators are used in Spring MVC in this section; instead, we focus on creating Validator implementations.

Validation in SpringBlog

Because SpringBlog is such a simple application, not much is required in the way of validation logic. However, we do perform simple validation on Comment and Entry objects that are to be added to the database. In Listing 11-22, you can see the Validator we created for the Entry object, EntryValidator.

Listing 11-22: The EntryValidator Class

image from book
package com.apress.prospring.business.validators;      import org.springframework.validation.Errors; import org.springframework.validation.ValidationUtils; import org.springframework.validation.Validator;      import com.apress.prospring.domain.Entry;      public class EntryValidator implements Validator {          public boolean supports(Class clazz) {         return clazz.isAssignableFrom(Entry.class);     }          public void validate(Object obj, Errors errors) {         Entry entry = (Entry) obj;                  ValidationUtils.rejectIfEmptyOrWhitespace(errors, "subject",                 "required", null, "required");                  ValidationUtils.rejectIfEmptyOrWhitespace(errors, "body", "required",                 null, "required");     }      }
image from book

Here you can see that we use the ValidationUtils class to validate that both the subject and body properties of the Entry object are supplied, that they are not empty, and that they do not contain just whitespace. You may be wondering how the ValidationUtils class accesses the value of those properties. They are stored in the Errors object that is passed in and can

be accessed using Errors.getFieldValue(). The second argument passed to the rejectIfEmptyOrWhitespace() method is the error code. Spring uses this value to look up the error message to display from a ResourceBundle containing individual properties files for each language you wish to support. Listing 11-23 shows an example of the English property file used in the SpringBlog application.

Listing 11-23: Internationalized Error Messages

image from book
required=This field is required and cannot be empty
image from book

You will see how to go about configuring the ResourceBundle and your Validators in Chapter 17. The third argument passed to rejectIfEmptyOrWhitespace(), for which we have used null, allows you to pass arguments, as an Object[], to the Spring framework, the elements of which will be replaced for tokens in the error message. For instance, in the message "Hello {0}", the token {0} would be replaced with the 0th element in the Object[]. The final argument passed to rejectIfEmptyOrWhitespace() is the default error message to render if no error message can be found in the ResourceBundle.

This is about as complex as our validation logic gets, and unfortunately, the rejectIfEmptyOrWhitespace() method is about as much help as you are going to get from the current version of Spring. However, don't worry, plenty of tools can help you to implement more complex validation logic, such as Commons Validator, which is covered briefly in the next section.

Using Commons Validator

Commons Validator, available at http://jakarta.apache.org/commons/validator, provides a wide range of validation utilities for many different data types, ranging from different numerics like double and int, to credit card numbers and e-mail addresses. Before starting to create your own routines for validation, first explore what is available in Commons Validator. We have found that for 90 percent of the cases in our applications, Commons Validator has a prebuilt validation routine we can use.

What's in Store for Validation?

There is a lot of work underway to improve validation support in Spring. The first notable addition in the next release will be full integration of Commons Validator, including support for externally defined validation rules that are implemented using Commons Validator and a JSP taglib to add client-side validation in JavaScript using the Commons Validator JavaScript support. The second, although not yet confirmed, addition is rules-based validation. Work is currently underway to add rules support to Spring, and there has been much discussion on providing a Validator implementation that uses a rule set as the source for validation rules.

Business Tier Summary

In this section, we looked at the various points you should consider when designing and building a Business Tier for your Spring application. We looked at the SpringBlog Business Tier, and explored how small refactorings in your Business Tier can substantially improve the testability of your application. When looking at method granularity, we discussed the natural granularity level of methods and we considered the impact that transaction demarcation has on your business interfaces. Finally, we finished this section by discussing validation and Spring support for it.

Overall, Spring doesn't provide much in the way of implementing actual business logic, but its support for Dependency Injection, validation, and transaction management make it an ideal platform on which to build your Business Tier.



Pro Spring
Pro Spring
ISBN: 1590594614
EAN: 2147483647
Year: 2006
Pages: 189

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net