The road to a fully encapsulated layered business model

History
About 20 years ago, during the time that I was working on my bachelor, I came in contact with a small company selling wall decorations. They had a need for some simple software, so I wrote a MSAccess application for them. During the years they grew and so did their software requirements, which resulted in a major overhaul about 10 years later when the whole code base was moved to Java 1.2 on top of an Informix database (back then considered a real competitor of Oracle’s RDBMS). The best way to access the database was using JDBC, so that was the approach that was chosen. Persistency frameworks were still immature (SDO) or expensive (Oracle’s Toplink).

The whole JDBC-combined-with-Swing did not work really well, partially because I had not figured Swing out when setting up the application’s architecture, but also because Swing uses objects and I had resultsets. Jumping forward another 5 years or so and persistency frameworks finally became an affordable foundation, so it was time to slowly migrate the code base over to Toplink (which soon was renamed to Eclipselink). Using Eclipselink made my Swing life that much easier, more than I expected, but also introduced new challenges.

reinders webshop
reinders webshop

Up until then, using JDBC in the 10 years old style, business logic was spread throughout the application screens. Initially this was a nuisance, but it could be dealt with. But soon, because of the growth of the company, it became a problem; there were additional interfaces required on top of the database for EDIFACT, website, webshop, email data exchange and support of mobile devices. The whole thing had grown into a full-fledged ERP and all these interfaces needed to make sure business rules were followed; it became clear that another approach was needed.

I was not a fan of EJB2 because of the complex setup, so I wanted to stay close to the basic keep-it-simple architecture of stand alone environments; a fat Swing client and batch Java applications for all the outside communication. That meant that I had to develop the business model (BM) as a simple JAR that could be used in each environment.

Architecture
The first thing that struck me as very odd was the fact that in the domain driven design (or model driven, I’m not sure where one ends and the other starts) documents I was reading, teaching was that entities basically were nothing more than glorified C-structures, only holding data from the database, decorated with some getters and setters. External controllers then would hold the actual logic… All very EJB focused with its entity and session beans and automatic transactions. The setup felt so not object-oriented (totally lacking encapsulation) that I refused to adopt this approach; entities were to be objects which could actually do things.

Secondly in DDD the persistency logic is not part of the BM, but somehow bolted onto the side. That also caused some short circuiting in my head; it seemed somewhat like writing a database application without knowing what database it was using. If the BM could only rely on some common API, it would be impossible to take advantage of the strengths of the persistency layer. Don’t get me wrong, I’m a big fan of standardisation, but also a fan of achieving my goal, and some choices must be balanced against reality. So I decided that the BM would have full knowledge of, access to and fully use the chosen persistency layer, being Toplink / Eclipselink / JPA.

To summarize; the business model should be a fully encapsulated model (JAR) that could be dropped-in in any type of application.

Implementation
Inheritance
Having set the architectural stage, the work on the BM went smoothly. Being lazy by nature, I decided that when I added a field to the database, I was not going to duplicate the work by also adding setters and getters. So after the initial trials a reverse code generator was created, that generated the required Java code from the database. Since that code was generated over and over again, the actual business code needed to go somewhere else. So a layer was put on top for the business rules and underneath was a common “bean” class for handling property change listeners and other Java beany things:

  • @Entity: business logic, generated once never overwritten
  • @MappedSuperclass: reversed engineered fields, overwritten every time
  • generic bean logic
@Entity
@Table(name="address")
public class Address extends nl.reinders.bm.generated.Address
implements java.io.Serializable
{ ... }

@MappedSuperclass
abstract public class Address extends nl.reinders.bm.AbstractBean<nl.reinders.bm.Address>
implements java.io.Serializable
         , java.lang.Cloneable
         , Comparable<nl.reinders.bm.Address>
{ ... }

This was the point where the decision for Eclipselink was made, because Hibernate did not support this setup (at that time?).

Relationships
Not only does the reverse generator take care of my simple fields, but also the inter-entity relations using the foreign keys; JPA dictates that the BM is responsible for maintaining the two sides of a relationship (scaffolding galore). So for example in a one-to-many relation, JPA does nothing to make sure the single reference on one side and the collection on the other are synced. Luckily the generated code takes care of that. And when some relation is not meant to be maintained (for example with lookup tables, those cause big memory and performance issues if they are fully wired up in the BM), the reverse generator does not generate the collection but replaces it with convenient find-methods. The entity inheritance stack works very well and could fairly easily be adapted to every situation I came across so far.

The generator creates a lot of scaffolding and supporting static variables intended to be used by the applications and thus allowing for compile time checking, but note that you normally never modify or even read this code. I’ve included one example of a 1-N relation below, but the code one normally writes looks like this:

	/**
	 * Must clear the image properties since they depend on the articlenr
	 * @see generated.Article#setArticlenr(java.lang.Integer)
	 */
	@Override
	public void setArticlenr(BigInteger value)
	{
		setImageIcon(null);
		setImageIconThumbnail(null);
		super.setArticlenr(value);
	}

For illustration purposes a 1-N example of the generated code:

class Address { 

	/** Relation */
	public nl.reinders.bm.Relation getRelation() { return iRelation; }
	public void setRelation(nl.reinders.bm.Relation value)
	{
		if (isReadonly() == true) return;
		if (value == iRelation) return; // optimalisation and prevent looping
		nl.reinders.bm.Relation lValue = iRelation; // remember old value for PCE
		if (!nl.knowledgeplaza.util.ObjectUtil.equals(lValue, value)) nl.reinders.bm.BM.failIfNoPermission(Address.class, "relation");
		if (log4j.isDebugEnabled()) log4j.debug("setRelation: " + lValue + " -> " + value);
		fireVetoableChange(RELATION_PROPERTY_ID, lValue, value);
		if (lValue != null) lValue.removeAddressesWhereIAmRelation( (nl.reinders.bm.Address)this );
		iRelation = value;
		try {
			if (value != null) value.addAddressesWhereIAmRelation( (nl.reinders.bm.Address)this );
		} catch (RuntimeException e) { iRelation = lValue; throw e; } // restore upon exception
		if (!nl.knowledgeplaza.util.ObjectUtil.equals(lValue, value)) markAsDirty(true);
		firePropertyChange(RELATION_PROPERTY_ID, lValue, value);
	}
	public nl.reinders.bm.Address withRelation(nl.reinders.bm.Relation value) { setRelation(value); return (nl.reinders.bm.Address)this; }
	@ManyToOne(fetch = FetchType.LAZY, targetEntity = nl.reinders.bm.Relation.class, cascade = {CascadeType.REFRESH} ) @JoinColumn(name="relationnr")
	volatile protected nl.reinders.bm.Relation iRelation;
	final static public String RELATION_COLUMN_NAME = "relationnr"; // for when building SQL or starting reporting tools
	final static public String RELATION_FIELD_ID = "iRelation";
	final static public String RELATION_PROPERTY_ID = "relation";
	final static public Class<nl.reinders.bm.Relation> RELATION_PROPERTY_CLASS = nl.reinders.bm.Relation.class;
	final static public boolean RELATION_PROPERTY_NULLABLE = true;
	// to make IN queries possible
	@Column(name="relationnr", insertable=false, updatable=false)
	volatile protected java.math.BigDecimal iRelationnr = null;
	final static public String RELATIONNR_COLUMN_NAME = "relationnr"; // for when building SQL or starting reporting tools
}
class Relation {
	/** AddressesWhereIAmRelation */
	public void addAddressesWhereIAmRelation(nl.reinders.bm.Address value)
	{
		if (isReadonly() == true) return;
		if (value != null && !iAddressesWhereIAmRelation.contains(value))
		{
			java.util.List<nl.reinders.bm.Address> lValue = new java.util.ArrayList<nl.reinders.bm.Address>();
			lValue.addAll(iAddressesWhereIAmRelation);
			lValue.add(value);
			fireVetoableChange(ADDRESSESWHEREIAMRELATION_PROPERTY_ID, java.util.Collections.unmodifiableList(iAddressesWhereIAmRelation), java.util.Collections.unmodifiableList(lValue));
			boolean lWasAdded = iAddressesWhereIAmRelation.add(value);
			lValue.remove(value);
			firePropertyChange(ADDRESSESWHEREIAMRELATION_PROPERTY_ID, java.util.Collections.unmodifiableList(lValue), java.util.Collections.unmodifiableList(iAddressesWhereIAmRelation));
			try {
				value.setRelation( (nl.reinders.bm.Relation)this);
			} catch (RuntimeException e) { if (lWasAdded) {iAddressesWhereIAmRelation.remove(value);} throw e; } // restore upon exception
		}
	}
	public void removeAddressesWhereIAmRelation(nl.reinders.bm.Address value)
	{
		if (isReadonly() == true) return;
		if (value != null && iAddressesWhereIAmRelation.contains(value))
		{
			java.util.List<nl.reinders.bm.Address> lValue = new java.util.ArrayList<nl.reinders.bm.Address>();
			lValue.addAll(iAddressesWhereIAmRelation);
			lValue.remove(value);
			fireVetoableChange(ADDRESSESWHEREIAMRELATION_PROPERTY_ID, java.util.Collections.unmodifiableList(iAddressesWhereIAmRelation), java.util.Collections.unmodifiableList(lValue));
			boolean lWasRemoved = iAddressesWhereIAmRelation.remove(value);
			lValue.add(value);
			firePropertyChange(ADDRESSESWHEREIAMRELATION_PROPERTY_ID, java.util.Collections.unmodifiableList(lValue), java.util.Collections.unmodifiableList(iAddressesWhereIAmRelation));
			try {
				value.setRelation( (nl.reinders.bm.Relation)null );
			} catch (RuntimeException e) { if (lWasRemoved) { iAddressesWhereIAmRelation.add(value); } throw e; } // restore upon exception
		}
	}
	public void setAddressesWhereIAmRelation(java.util.List<nl.reinders.bm.Address> value)
	{
		// first do a standard setter
		if (isReadonly() == true) return;
		if (value == iAddressesWhereIAmRelation) return; // optimalisation and prevent looping
		java.util.List<nl.reinders.bm.Address> lValue = iAddressesWhereIAmRelation;
		if (log4j.isDebugEnabled()) log4j.debug("setAddressesWhereIAmRelation: " + lValue + " -> " + value);
		fireVetoableChange(ADDRESSESWHEREIAMRELATION_PROPERTY_ID, java.util.Collections.unmodifiableList(lValue), java.util.Collections.unmodifiableList(value));
		iAddressesWhereIAmRelation = value;
		if (!nl.knowledgeplaza.util.ObjectUtil.equals(lValue, value)) markAsDirty(true);
		firePropertyChange(ADDRESSESWHEREIAMRELATION_PROPERTY_ID, java.util.Collections.unmodifiableList(lValue), java.util.Collections.unmodifiableList(value));

		// then update the other side
		// 1. which entities are no longer in the collection
		if (lValue != null) { // scan the old collection
			for (nl.reinders.bm.Address lOther : lValue) {
				if (value == null || !value.contains(lOther)) { // if the new collection is empty or does not contain this entity
					lOther.setRelation( (nl.reinders.bm.Relation)null ); // unlink
				}
			}
		}
		// 2. which entities are now in the collection
		if (value != null) { // scan the new collection
			for (nl.reinders.bm.Address lOther : value) {
				if (lValue == null || !lValue.contains(lOther))  { // if the old collection is empty or does not contain this entity
					lOther.setRelation( (nl.reinders.bm.Relation)this  ); // link
				}
			}
		}
	}
	public nl.reinders.bm.Relation withAddressesWhereIAmRelation(java.util.List<nl.reinders.bm.Address> value) { setAddressesWhereIAmRelation(value); return (nl.reinders.bm.Relation)this; }
	/** returns a new list containing a snapshot of the actual list, instead of an returning an unmodifyableList. Either require a new object to be created, but the snapshot allows a.o. sorting without creating another object. Changes to the list are NOT reflected in the BM and vice versa! */
	public java.util.List<nl.reinders.bm.Address> getAddressesWhereIAmRelation() { return new java.util.ArrayList<nl.reinders.bm.Address>(iAddressesWhereIAmRelation); }
	final static public String ADDRESSESWHEREIAMRELATION_FIELD_ID = "iAddressesWhereIAmRelation";
	final static public String ADDRESSESWHEREIAMRELATION_PROPERTY_ID = "addressesWhereIAmRelation";
	final static public Class<nl.reinders.bm.Address> ADDRESSESWHEREIAMRELATION_PROPERTY_CLASS = nl.reinders.bm.Address.class;
	@OneToMany(mappedBy = "iRelation", fetch = FetchType.LAZY, targetEntity = nl.reinders.bm.Address.class, cascade = {CascadeType.REFRESH,CascadeType.PERSIST,CascadeType.MERGE,CascadeType.REMOVE} )
	volatile protected java.util.List<nl.reinders.bm.Address> iAddressesWhereIAmRelation = new java.util.ArrayList<nl.reinders.bm.Address>();

Context
One other thing turned out to be a big issue; context. Suppose you have a swing application with two screens (JFrames) open. Both screens show the same entity, for example a customer. If you make a change to the entity in one screen, then common usage does not expect the data on the other screen to be affected by that change, at least not until the change is saved to the database. So changes are restricted to the screen they happen on. The initial trials used a global context, and that resulted in a lot of inter-screen conflicts, which were not understood by the actual users. So now entities live within the context of a screen, or in JPA words: a single EntityManager is associated with each screen. This means that the same entity can be loaded into memory more than once, each within its own context.

Reinders ERP
Reinders ERP

The BM knows fully what persistency it uses, but is totally unaware of what application it is used in. So if the BM is used in the fat Swing application, context is determined by the screens. However, if the BM is used in a batch process like the EDIFACT interface, there are no screens and there is only one context for the whole application. But if the Swing application decides to spawn a background process, the thread will have a context (EM) on its own.

The context setup is even more complicated by the architectural choice that the BM is to be encapsulated. This meant that the BM should do whatever it needs to in order to get the work done, including adding and removing entities. So if some action on entity A required the creation and persisting of an entity B, the BM needs to be able to get to the EntityManager in order to call “persist”. But since it doesn’t know what application it was running in, the concept of EntityManagerFinder (EMF) was introduced. An EntityManagerFinder has only one static method, being “find()” which returns the EntityManager for the context it is called in.

A good example for using the EMF is for searching. Methods for finding entities are available as static methods in the entity classes for example Article.findByEAN(“1234567890128”):

	/**
	 * @param ean
	 */
	static public java.util.List<nl.reinders.bm.Article> findAllByEAN(String ean)
	{
		EntityManager lEntityManager = nl.knowledgeplaza.util.jpa.EntityManagerFinder.find();
		Query lQuery = lEntityManager.createQuery("select t from Article t where t." + EAN_FIELD_ID + "=:ean");
		lQuery.setParameter("ean", ean);
		java.util.List<nl.reinders.bm.Article> lList = lQuery.getResultList();
		return lList;
	}

There are a number of EMF implementations created:

  • Singleton: one global EM in the whole JVM, used in batch processes
  • Thread: one EM per thread
  • Swing: one EM per JFrame, but extending on the Thread to allow for background processes

The thread EMF does not work in Swing applications, because all UI interaction in Swing is done from one thread (the EDT) but may involve different screens.

The catch: persistency events
This setup has served me very well in the last few years, often being totally invisible and just working. Sometimes, when I spawn a background process and use the wrong SwingWorker implementation I get reminded that there is a lot of logic underneath handling the entity’s universe. There only is one thing that has not been resolved: events.

99% of all business logic is easily implemented overriding setters, or reacting to property change events, but sometime things need to be done upon persistence events. The problem is JPA events are only meant for things happening inside the same entity, like update an age when the birth date is changed. But you should not do anything to other entities, because those actions may or may not be persisted, depending on if the targetted entity already has been visited by the persisting logic.

A good example in this case is the actual allocating of items from the stock the moment the order is persisted. This must be done inside the persisting logic, because the logic uses database locks to prevent other processes from also allocating the same stock amount and that requires running inside a transaction. Also, if the allocation fails because of stock issues, the transaction’s rollback handles the cleaning up.

I’ve tried using Eclipselink’s native events, which are far more fine grained than JPA’s, but this is something that I was not able to solve. So in the end it comes down to the application layer having to call the appropriate code during the steps of the persistancy. And these calls must be duplicate to other applications which call the same BM logic. Not an ideal situation.

The expert group
So implementing a fully encapsulated business model is almost a success, it’s just that one thing that is missing. By the time of writing of this blog, the export group has started work on the next version of JPA, and I have submitted the request for an improved event / callback mechanisme which allows modifications to other entities. But if someone has the holy grail of JPA events, and can make my problems go away, please send me a mail!

This Post Has 2 Comments

  1. intellopitt

    Thank you for sharing your experience. I really appreciate the quality of the content.
    Actually I’m facing more or less the same challenge, I have a small Cash Register application using Swing & Jdbc, and I’m planning to migrate to an JavaFX / JPA one.
    But, I still have some doubt about using JavaFX 2 since it doesn’t support yet RTL languages.

    1. tbeernot

      If you plan to use a business model with setters and getters, then Swing combined with JGoodies binding is a very good choice. JavaFX definitely is the more powerful graphics framework, but the question is if you need that for a cash register. Plus JFX lacks good binding between JavaFX properties and old POJO style properties. There is an effort to allow such binding in JFXtras, but you would be walking on the edge of development there.

      Never the less, using a separate business model is something that I can whole heartely recommend.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.