$Author: bastafidli $
$Date: 2007/03/11 06:30:45 $
$Revision: 1.25 $
$RCSfile: tutorial_businesslogic.html,v $
Business logic is what makes each application unique and represents the heart and brain of the software system. In general, it is responsible for the processing of data utilizing the persistence tier. The same business logic should apply regardless if the application is presented to the user in a browser, as a desktop application or using a PDA. That said, the business logic provides services to the user interface tier and therefore the designer must consider how these services will be used to make the implementation of different kinds of clients easier.
Best practices tell us to separate the presentation layer from the business logic by interfaces making it independent from the specific implementation. Another good practice is to make the business logic stateless and therefore easily deployable in a clustered environment. This makes the application more scalable and allows it to support larger number of users. Open Core adopts these best practices and provides set of generic high level interfaces and stateless base classes to make the design and implementation of your business logic easier. The provided classes define and often provide basic implementation for the methods necessary to access and process the data objects.
We will decide, what type of processing is required by our application and derive from the appropriate interfaces and classes our own ones. Once that's done, we will add to our newly defined interfaces any methods to process data in a way unique to our application. Then we just need to implement any of our custom methods and optionally override the default implementation of those provided by Open Core that doesn't match our needs. Later on, we will use controller manager to create and access the implementation of these interfaces in our user interface tier.
StatelessController
and StatelessControllerImpl
- interface and base class that should be implemented by
all business logic modules acting as
business
delegates. By utilizing these as a base for your
business logic interfaces and classes your application can
access them regardless if they are deployed as a simple
Java objects (POJOs) or as an enterprise beans in J2EE
environment (EJBs).
DataController
and DataControllerImpl
- interface and base class for all controllers managing
data objects. The primary purpose of the data controller
is to implement additional functionality on top of the
persistence layer such as security checks, logging, data
processing etc. Every data object should be available to
the presentation tier somehow and controller that makes
data objects accessible to the rest of the system should
implement this interface and derive from this base class.
BasicDataController
and BasicDataControllerImpl
- interface and base class that provide the most basic
operations to perform with data objects in an application.
Each data object needs to be somehow created and removed
(if not for anything else at least for automated tests)
and you gain such capabilities by deriving your interfaces
and classes from these ones.
ModifiableDataController
and ModifiableDataControllerImpl
- interface and base class that add support and simple
implementation of methods allowing data object
modification.
ControllerManager
- factory class responsible for instantiation of controllers.
User interface and other controllers use controller manager
to get references to your controller to execute the business
logic it provides. This class determines what environment
the application is running in (J2EE, POJO, etc.) and what
is the desired access model (local interfaces, direct
references, etc.) and creates the controller instances.
This way it acts as a
service
locator for services provided by the controllers. It
utilizes
ClassFactory
derived class to implement the
strategy
pattern to express the exact mechanism how to determine
what class to instantiate.
The business logic of OpenChronicle is defined in
BlogController
interface and implemented in
BlogControllerImpl
class. These are derived from
ModifiableDataController
and ModifiableDataControllerImpl
because both
Blog
and
Entry
data objects can be modified by user maintaining his or her
chronicle.
Our simple application doesn't require very much functionality. In our case the main purpose of the controller is to coordinate both factories and provide data required by the user interface efficiently, usually with a single method call. The single method call idea follows the Session Facade pattern. For example, the page displaying list of all entries in chronicle will need both, the list of Entry objects and the Blog object since it will be nice to show to the user what is the name of the chronicle the entries belong to. The controller interface therefore provides method
/**
* Get blog and its entries knowing just the folder where it's entries
* are displayed.
*
* @param strFolder - folder where entries for given folder are displayed
* @return Object[] - index 0 is blog and index 1 is list of its entries
* @throws OSSException - an error has occured
* @throws RemoteException - required since this method can be called remotely
*/
Object[] getWithEntries(
String strFolder
) throws OSSException,
RemoteException;
and controller class provides its implementation
/**
* {@inheritDoc}
*
* @ejb.interface-method
* @ejb.transaction type="Supports"
*/
public Object[] getWithEntries(
String strFolder
) throws OSSException
{
Object[] returnValue = {null, null};
Blog blog;
List lstEntries;
// This demonstrate important technique and that is session facade pattern
// One call to controller is used to fetch multiple pieces of data.
// This is important if controller is remote from the presentation tier
// and then we want to perform only as little remote calls as possible.
blog = m_blogFactory.get(strFolder);
if (blog != null)
{
returnValue[0] = blog;
lstEntries = m_entryFactory.getAll(blog.getId());
if (lstEntries != null)
{
returnValue[1] = lstEntries;
}
}
return returnValue;
}
The controller coordinates the data factories to retrieve the
data. Since the data factories are stateless and therefore
thread safe, controller caches them in member variables.
These are populated in method called constructor.
The main reason for such oddly named method is that the
controller can be run as a stateless session EJB. EJBs are
not allowed to have class constructors. Open Core provides this
method to perform the same tasks that would be normally done
in class constructors. Open Core invokes the constructor method
as soon as the controller manager creates the controller
instance and before any other method can be invoked.
/**
* Factory to use to execute persistence operations.
*/
protected BlogFactory m_blogFactory = null;
/**
* Factory to use to execute persistence operations.
*/
protected EntryFactory m_entryFactory = null;
/**
* {@inheritDoc}
*
* @ejb.interface-method
* @ejb.transaction type="Supports"
*/
public void constructor(
) throws OSSException
{
m_blogFactory = (BlogFactory)DataFactoryManager.getInstance(BlogFactory.class);
m_entryFactory = (EntryFactory)DataFactoryManager.getInstance(EntryFactory.class);
}
Looking at the controller source code you may notice the lack of methods that allow modifying the data. The ability to create, modify and delete the data objects is provided by the parent classes, from which our controller is derived. If you wonder how do the parent classes know, what data objects to manipulate, notice that the controller implements two abstract methods defined in these classes.
/**
* {@inheritDoc}
*/
protected DataFactory getDataFactory(
)
{
// Blog is the default entity for this controller so return the blog factory
return m_blogFactory;
}
/**
* {@inheritDoc}
*/
protected BasicDataFactory getDataFactory(
DataObject data
)
{
BasicDataFactory factory = null;
if (GlobalConstants.ERROR_CHECKING)
{
assert data != null : "Cannot return factory for null data object";
}
if (data instanceof Blog)
{
factory = m_blogFactory;
}
else if (data instanceof Entry)
{
factory = m_entryFactory;
}
else
{
if (GlobalConstants.ERROR_CHECKING)
{
assert false : "Cannot return factory for unrecognized data type "
+ data.getClass().getName();
}
}
return factory;
}
These two methods tell the parent classes what factory to use
based on the method arguments. This work just fine for
methods that take a data object as their argument but it
doesn't work for those that take just a data object id. Example
of such method is method delete
/**
* Delete data object.
*
* @param iId - id of the data object to delete
* @throws OSSException - an error has occured
* @throws RemoteException - required since this method can be called remotely
*/
void delete(
int iId
) throws OSSException,
RemoteException;
declared in
BasicDataController
interface. Because our controller supports two types of data
objects, Blogs and Entries, we have to provide counterparts of
these methods for data objects that are not handled by default
in this controller. The BlogController has Blogs as its default
data object as described in the comment of the
getDataFactory method and we have to provide
custom method
/**
* Delete entry. We have to define separate method for this operation since
* the default data type for this controller is blog and therefore the default
* implementation will just delete blogs.
*
* @param iId - id of the entry to delete
* @throws OSSException - an error has occured
* @throws RemoteException - required since this method can be called remotely
*/
void deleteEntry(
int iId
) throws OSSException,
RemoteException;
/**
* {@inheritDoc}
*
* @ejb.interface-method
* @ejb.transaction type="Required"
*/
public void deleteEntry(
int iBlogEntryId
) throws OSSException
{
m_entryFactory.delete(iBlogEntryId,
CallContext.getInstance().getCurrentDomainId());
}
to allow deletion of Entries.
It has been mentioned several times that controllers can be deployed as plain old Java objects (POJO) or as a stateless session enterprise java beans (EJB). To run the business logic as POJO, the only thing we need is Java Runtime Environment (JRE) and the simple java code we have already demonstrated. To run it as EJB the application needs to be deployed using one of the supported J2EE application servers. Open Core controller manager, which is responsible for controller creation (instantiation) supports both options. Open Core is using XDoclet to generate the classes and files required by J2EE application servers. To take advantage of this functionality, we just need to add the XDoclet tags to the JavaDoc comments in the controller implementation class and it's public methods. This is only necessary if we want to run your application's business logic as EJB, the business logic will function just fine when deployed in J2EE application server as POJO even without these tags.
The class level tags tell XDoclet what type of EJB to generate, how to name it and what other EJBs it is using
/**
* The main entry point to all business functionality connected with blogs.
*
* View-type has to be set to local due to bug XDT-867 affecting WebSphere
* Refs has to be set to local JNDI name since we do not want to use remote objects.
*
* @ejb.bean type="Stateless"
* name="BlogController"
* view-type="local"
* jndi-name="org.opensubsystems.blog.logic.BlogControllerRemote"
* local-jndi-name="org.opensubsystems.blog.logic.BlogController"
* @ejb.interface
* local-extends="javax.ejb.EJBLocalObject, org.opensubsystems.blog.logic.BlogController"
* extends="javax.ejb.EJBObject, org.opensubsystems.blog.logic.BlogController"
*
* @jonas.bean ejb-name="BlogController"
* jndi-name="org.opensubsystems.blog.logic.BlogControllerRemote"
*
*/
public class BlogControllerImpl extends ModifiableDataControllerImpl
implements BlogController
{
...
The method level tags tell XDoclet if the method requires transaction. All public methods modifying data in the persistence store should require transactions.
/**
* {@inheritDoc}
*
* @ejb.interface-method
* @ejb.transaction type="Required"
*/
public void deleteEntry(
int iBlogEntryId
) throws OSSException
{
m_entryFactory.delete(iBlogEntryId,
CallContext.getInstance().getCurrentDomainId());
}
All public methods merely reading the data should support transactions in case they are called within a transaction that modifies data.
/**
* {@inheritDoc}
*
* @ejb.interface-method
* @ejb.transaction type="Supports"
*/
public List getEntries(
int iBlogId
) throws OSSException
{
List lstEntries;
lstEntries = m_entryFactory.getAll(iBlogId);
return lstEntries;
}
The business logic is now implemented and it is time to take a look at how to create user interface for our application.
Next:
Constructing the web application
Previous:
Implementing the persistence layer