Implementing Business Logic

As a professional developer, you know how to program in Java. What I want to discuss here is how to put an application together and how to focus your programming skills on your application's domain. Thus the following discussion of the sample application does not address low-level details such as the implementation of the adjacent seating algorithm, but focuses on how the sample application's components are configured and work together.

We'll look both at how the sample application uses standard J2EE infrastructure and how it uses the infrastructure we've discussed so far in this chapter.

The sample application's code is available in the download accompanying this book. Please refer to it as necessary during the following discussion, as it's impractical to provide a complete listing of all the classes mentioned.

Implementing the Sample Application

Let's now consider some of the key business logic of the sample application. Let's focus on two key issues: how we obtain reference data about genres, shows, and performances; and how we obtain information about seat availability and implement the booking process.

These use cases are described in detail in Chapter 5, so I'll just provide a quick summary of the requirements here, along with the high-level design decisions taken in the last few chapters concerning them.

Genre, show, and performance data changes rarely and can be shared between all users of the application. The retrieval of this data need not be transactional.

The booking process requires transactional data access, and involves the holding of user session state. We have chosen to hold user session state in an HttpSession object in the web container.

Defining Business Interfaces

The first step is to define the necessary business interfaces. These will not be web-specific; however, they will run in the web container in a collocated application.

These interfaces should be able to be implemented with or without EJB without affecting code that uses them. In a collocated application such as our sample application, we'll use the Service Locator pattern, discussed above, to hide the details of EJB access completely.

There is only one area in which retaining the option of using EJB affects our design: exception handling. As EJBs handle unchecked exceptions as fatal system exceptions, as we saw in Chapter 10, we are unable to use unchecked exceptions in a business interface that might be implemented by an EJB. Occasionally this calls for compromises.

Consider the case of a request to find the number of seats free for a performance, given a performance ID that doesn't match any performance. Such a request cannot occur in our application, as the user can only request availability using links from pages that will provide valid performance IDs. Thus it makes no sense to complicate application code by catching a checked NoSuchPerformanceException.

However, given that we would like the ability to implement our BoxOffice interface using EJB, we must choose the lesser of the following two evils: make all our application exceptions (including NoSuchPerformanceException) checked and cope with the overhead of catching checked exceptions where this serves no useful purpose; or lose the ability to switch between EJBs and other business object implementations transparently. In the present application we opt for the use of checked exceptions. We could use the Business Delegate pattern, to catch checked exceptions from EJBs and rethrow unchecked exceptions. However, this requires more work to implement.

In the sample application, we will want two separate interfaces to handle the functionality we're considering here: one to deal with reference data, and one to deal with availability and the booking process. Let's consider each in turn.

The com.wrox.expertj2ee.ticket.referencedata.Calendar interface handles reference data requirements:

    public interface Calendar {      List getCurrentGenres();      Show getShow(int id) throws ReferenceDataException;      Performance getPerformance(int id) throws ReferenceDataException;    } 

The Genre,Show, and Performance objects are lightweight value objects, disconnected from the data source. They expose a tree structure, navigable downwards: from a genre we can find shows, and from each show we can get a list of performances. The entire tree can be cacheable for up to one minute, or as long as we know that there has been no change to reference data (the complexity of caching objects individually is unwarranted, as it would deliver little benefit).

The com.expertj2ee.ticket.boxoffice.BoxOffice interface exposes information about seating availability and exposes the booking process. It includes the following methods:

    public interface BoxOffice {      int getSeatCount (int performanceId) throws NoSuchPerformanceException;      int getFreeSeatCount (int performanceId)        throws NoSuchPerformanceException;      Reservation allocateSeats (ReservationRequest request)        throws NotEnoughSeatsException, NoSuchPerformanceException        InvalidSeatingRequestException;      Booking confirmReservation (PurchaseRequest purchaseRequest)        throws ExpiredReservationTakenException,        CreditCardAuthorizationException,        InvalidSeatingRequestException,        BoxOfficeInternalException;    } 

The first two methods expose seat availability. The allocateSeats() method creates a reservation, while the confirmReservation() method turns an existing reservation into a purchase. There is little opportunity for caching here, and the two booking methods are transactional.

The ReservationRequest object, used as a parameter to the allocateSeats() method, is a command, as is the PurchaseRequest parameter to the confirmReservation() method. The use of command objects has the following advantages, where multiple individual parameters would otherwise be required:

  • It's possible to add parameter data without breaking method signatures.

  • Commands provide an excellent basis for event publication if necessary, using JMS or a simple Observer implementation. For example, we could easily publish an event with a command attached to enable an e-mail to be sent when a user makes a successful reservation.

  • It may be possible to bind HTTP request parameters values onto a command. Most MVC web application frameworks can automatically populate JavaBean properties of command objects from HTTP request parameters. This can greatly simplify application request processing code in the web tier.

A listing of the ReservationRequest command shows that such commands are merely simple data holders. This object is serializable not to enable remote invocation, but to allow it to be stored in an HttpSession, which may need to be replicated across a cluster or written to a persistent store if swapped out of application server RAM:

    public class ReservationRequest implements Serializable {      private static final long MILLIS_PER_MINUTE = 1000L * 60L;      private int performanceID;      private int seatsRequested;      private double bookingFee;      private int classID;      private boolean reserve;      private Date holdTill;      private boolean mustBeAdjacent;      public ReservationRequest() {      }      public ReservationRequest (int performanceID, int classID,          int seatsRequested, boolean reserve, double bookingFee,          int minutesToHold)          throws InvalidSeatingRequestException {        this.performanceID = performanceID;        this.classID = classID;        this.seatsRequested = seatsRequested;        this.reserve = reserve;        this.bookingFee = bookingFee;        holdFor (minutesToHold);      }      public int getPerformanceID() {        return performanceID;      }      public void setPerformanceID(int performanceID) {        this.performanceID = performanceID;      }      public int getSeatsRequested() {        return seatsRequested;      }      public void setSeatsRequested(int seatsRequested) {        this.seatsRequested = seatsRequested;      }      public boolean getSeatsMustBeAdjacent() {        return mustBeAdjacent;      }      public void setSeatsMustBeAdjacent (boolean mustBeAdjacent) {        this.mustBeAdjacent = mustBeAdjacent;      }      public double getBookingFee() {        return bookingFee;      }      public void setBookingFee (double bookingFee) {        this.bookingFee = bookingFee;      }      public void holdFor(int minutes)          throws InvalidSeatingRequestException {        if (holdTill != null)          throw new InvalidSeatingRequestException (            "holdFor is immutable: cannot reset");        holdTill = new Date(System.currentTimeMillis() +                            minutes * MILLIS_PER_MINUTE);      }      public Date getHoldTill() {        return holdTill;      }      public int getClassID() {        return classID;      }      public void setClassID(int classID) {        this.classID = classID;      }      public boolean isReserve() {        return reserve;      }      public void setReserve (boolean reserve) {        this.reserve = reserve;      }    } 

Business interface return values, such as the Reservation object, are coded to interfaces. This enables implementations to return an immutable implementation, to prevent manipulation - inadvertent or malicious - by calling code.

Determing Implementation Strategy

The Calendar interface involves non-transactional data access. Thus there is no need to implement it using an EJB; we can use DAOs in the web container.

The BoxOffice interface involves transactional data manipulation. Thus a stateless session bean with a local interface is an obvious implementation choice, because using CMT can simplify application code.

These decisions will not affect code that uses these objects. Thus if we wish to increase or decrease use of EJB in the future, or use a different data-access strategy, there will be limited impact on the codebase.

Let's consider the implementation of the Calendar interface first. The com.wrox.expertj2ee.ticket.referencedata.jdbc.JdbcCalendar class provides a threadsafe implementation using the JDBC abstraction packages discussed in Chapter 9. The source code is similar to that shown in Chapter 9. The JdbcCalendar class implements the com.interface21.beans.factory.InitializingBean interface, enabling it to load data in its initialization of the afterPropertiesSet() method.

As all other application objects are coded to use the Calendar interface, rather than the JdbcCalendar implementation, caching can be handled by the com.wrox.expertj2ee.ticket.referencedata.support.CachingCalendar class, which proxies any underlying Calendar implementation (such as a JdbcCalendar) and uses copy-on-write to construct a new Calendar implementation when its refresh() method is invoked. Such use of a caching proxy is a natural idiom for interface-based design, which can significantly benefit performance while keeping caching logic separate from application implementation.

Implementing the BoxOffice

The BoxOfficeEJB uses two helper objects: the BoxOfficeDAO interface, which hides the details of data access behind an abstraction layer and which we looked at in Chapter 9; and the CreditCardProcessor interface, which provides the ability to process credit card payments. The sample application uses a dummy implementation of this interface; in a real application, an implementation would probably contact an external payment processing system.

The com.wrox.expertj2ee.boxoffice.ejb.BoxOfficeEJBLocal EJB component interface extends the BoxOffice interface (its "business methods" interface) as well as javax.ejb.EJBLocalObject, as required by the EJB specification:

    public interface BoxOfficeLocal extends BoxOffice, EJBLocalObject {    } 

The local home interface, com.wrox.expertj2ee.boxoffice.ejb.BoxOfficeHome, specifies the single create method required of all stateless session beans:

    public interface BoxOfficeHome extends EJBLocalHome {      BoxOfficeLocal create() throws CreateException;    } 

The bean implementation class will extend our com.interface21.ejb.support.AbstractstatelessSessionBean framework class and implement the BoxOffice business interface:

    public class BoxOfficeEJB extends AbstractStatelessSessionBean        implments BoxOffice { 

Extending AbstractStatelessSessionBean forces us to implement a method with the signature ejbCreate(), matching the create() method on the home interface, which we use to obtain and cache in instance variables helper objects defined in the bean factory:

    public void ejbCreate() {      logger .config("Trying to load data source bean");      this.dao = (BoxOfficeDAO)        getBeanFactory().getBean ("dao");      logger.config("Data source loaded OK: [" + this.dao + "]");      this.CreditCardProcessor = (CreditCardProcessor)        getBeanFactory().getBean ("creditCardProcessor");      logger.config("Credit card processing loaded OK: [" +                    this.CreditCardProcessor + "]");    } 

The corresponding bean definitions in the ejb-jar.xml deployment descriptor use theJNDI bean factory syntax described earlier in this chapter, as follows:

    <env-entry>      <env-entry-name>beans.dao.class</env-entry-name>      <env-entry-type>java.lang.String</env-entry-type>      <env-entry-value>         com.wrox.expertj2ee.ticket.boxoffice.support.jdbc.         JBoss30OracleJdbcBoxOfficeDao      </env-entry-value>    </env-entry>    <env-entry>      <env-entry-name>beans.creditCardProcessor.class</env-entry-name>      <env-entry-type>java.lang.String</env-entry-type>      <env-entry-value>         com.wrox.expertj2ee.ticket.boxoffice.support.DummyCreditCardProcessor      </env-entry-value>    </env-entry> 

Please see the download for a complete listing of this EJB. However, let's look at the implementation of the confirmReservation() method, which illustrates use of CMT. This method is marked in the deployment descriptor to run in a transaction. We use programmatic rollback via the setRollbackOnly() method of the SessionContext if we fail to authorize a credit card transaction. Before we attempt credit card authorization we attempt to create a booking. This makes it very unlikely that the user can be debited for a purchase, yet fail to be given a booking.

This method will throw only checked exceptions, as we want to preserve exceptions for clients:

    public Booking confirmReservation(PurchaseRequest purchase)      throws ExpiredReservationTakenException,             CreditCardAuthorizationException,             BoxOfficeInternalException,             InvalidSeatingRequestException { 

Before we attempt to authorize payment, we create a purchase record that will create a permanent record in the event of a later, unexpected failure:

    int purchaseId = dao.createPurchaseRecord(purchase.getReservation());    String authorization = null; 

We use CMT to roll this data update back if the credit card transaction is declined (in the highlighted code below). In the unlikely event that payment is accepted, but we fail to update the purchase record with the authorization code (a fatal internal error), we create a log record but throw a checked exception. Throwing a runtime exception would result in the transaction being rolled back, which would lose the new purchase record, which should remain in the database:

    try {      authorization = creditCardProcessor.process(        purchase.getReservation().getTotalPrice(),        purchase.getCardNumber(), purchase.getExpiry());      // Complete data update for purchase        dao.setAuthorizationCode(purchaseId, authorization);        Booking booking = new ImmutableBooking(          authorization, new Date(), purchase.getReservation());        logger.info("Booking successful [" + booking + "]");        return booking;      }      catch (CreditCardAuthorizationException ex) {        logger.severe("Failed to authorize credit card number");        getSessionContext().setRollbackOnly();        throw ex;      }      catch (DataAccessException ex) {        // Failed to authorize: we know we've created pending purchase record        //DO NOT ROLLBACK: this will lose PENDING record        // This is unusual: of course the tx may not commit, so we want to log        //as much information as possible as well        String mesg ="**** Database problem: failed to set authorization code "'          + authorization + " ' for purchase id " + purchaseId          + "; payment WAS accepted for reservation ["          + purchase.getReservation() + "] ****";        logger.severe(mesg);        // Must throw a checked exception to avoid automatic rollback        throw new BoxOfficeInternalException(mesg);      }    } 

By using EJB with CMT, we've simplified our application code: transaction management adds minimal complexity.

Note that we can catch the generic com.interface21.dao.DataAccessException described in Chapter 9 without introducing dependency on the use of JDBC to implement the BoxOfficeDAO data access interface.

Using JMS to Propagate Data Updates

For efficiency we choose to cache reference data until we know that it has been updated. However, such updates must work across a cluster of servers, so we cannot use the support for the Observer design pattern built into our application framework - we must use JMS. A JMS listener will cause cached data to be refreshed on messages to a data update topic. When the Admin interface is available, it will publish JMS messages on changes to reference data; before the Admin interface is completed, administrators will need to access a restricted URL that will cause a reference data update event to be published after updating the database.

Note 

Note that as the cached data isn't held in the EJB container, we cannot use an MDB as a message consumer.

We saw the implementation of the com.wrox.expertj2ee.ticket.referencedata.support.DataUpdateJmsListener class, which consumes JMS messages, in our discussion of JMS infrastructure above. This invokes the refresh() method of the CachingCalendar object, causing all reference data to be reread from the database.

Pulling It All Together

Now we have our interfaces and implementations, we can use our application framework to hook them up. The following excerpts from the ticket-servlet.xml application contextXML definition file illustrate how the application components we've discussed are wired up as JavaBeans.

The following two bean definitions define a Calendar implementation that uses JDBC, and a "caching calendar" that creates new objects using the realCalendar definition when it receives update events. Note that the singleton attribute of the realCalendar definition is set to false, so a new object will be returned each time the cachingCalendar asks for an instance of this bean definition:

    <bean name="realCalendar"      singleton="false"                                                                                                           >    </bean>    <bean name="calendar"      >    </bean> 

The following custom bean definition, discussed earlier, registers a JMS listener that notifies the CachingCalendar object when reference data is updated:

    <bean name="referenceDataListener"      definition>      <customDefinition property="listenerBean">          com.wrox.expertj2ee.ticket.referencedata.support.DataUpdateJmsListener      </customDefinition>      <customDefinition property="topicConnectionFactoryName">          jms/TopicFactory      </customDefinition>      <customDefinition property="topicName">          jms/topic/referencedata      </customDefinition>      <property name="cachingCalendar"beanRef="true">          calendar      </property>    </bean> 

The BoxOffice bean definition uses the dynamic proxy bean definition discussed above to hide the complexity of EJB access. We specify the business interface the EJB implements and the bean's JNDI name:

    <bean name="boxOffice"          definition> 

     <customDefinition property="businessInterface">          com.wrox.expertj2ee.ticket.boxoffice.BoxOffice      </customDefinition>      <customDefinition property="jndiName">          ejb/BoxOffice      </customDefinition> 
    </bean> 

There is no need for any application objects to perform JNDI lookups or use the EJB API directly: they can simply use the object created by this bean definition as an implementation of the com.wrox.expertj2ee.ticket.boxoffice.BoxOffice business interface.

Using the infrastructure discussed in this chapter, application components that use these interfaces need simply expose bean properties that can be set by the framework appropriately: they don't need to depend on framework code and they don't need to look up objects managed by the framework. For example, the application's main web-tier controller exposes calendar and boxOffice properties, which the framework sets automatically based on the following bean definition:

    <bean name="ticketController"      >      // Other properties omitted 

     <property name="calendar" beanRef="true">calendar</property>      <property name="boxOffice" beanRef="true">boxOffice</property> 

    </bean> 

The controller doesn't need to do any configuration lookup, but merely has to implement the following methods to save references to instance variables:

    public void setBoxOffice (BoxOffice boxOffice) {      this.boxOffice = boxOffice;    }    public void setCalendar (Calendar calendar) {      this.calendar = calendar;    } 

We will discuss the web tier in detail in the next chapter. As this example indicates, it is very closely integrated with the overall infrastructure we've examined.



Expert One-on-One J2EE Design and Development
Microsoft Office PowerPoint 2007 On Demand
ISBN: B0085SG5O4
EAN: 2147483647
Year: 2005
Pages: 183

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