Dynamic Entity Engine – OverviewAuthor: Rod Odin (Nikolay Chebotaryov)
Published: March, 2009
Русская редакция находится здесь.
MotivationDo you know about the Heroes of Might and Magic game, or probably about Civilization? Or about another game with lots of
... [More]
different units like Dragons, Knights, Archers, or maybe Tanks, Super-sonic Fighters and Missile Cruisers? Or probably you have a business application with dozens of different business entities to be processed by your business logic?
If you have to develop something like this, how would you like to act? Would you manually create dozens or hundreds of appropriate classes with dozens of getters/setters representing the entity properties? Of course you are a familiar OO-developer and you will build a well organized hierarchy of the entities (or game units). And if your entities must be stored persistently, of course, you will map them onto the relational tables using JPA-annotations. And then you will write tones of the business logic (dozens of session beans) to manage and process somehow all of these plain entities – the entities just consisting of dozens of properties defined using simple getter/setter methods.
What would you to do if your business logic must intercept events like propertyChanged or propertyAccessed? You have, for instance, 50-60 different entities with about of 10 properties per each on average. And you need to intercept different events like propertyChanged or entitySaved sometimes only. Would you add an event listener support into the each getter/setter method where it is required? Or would you add the listener support into all getters/setters just because you don't know when and where it would be useful?
And again about the well-organized classical object-oriented hierarchy... For example, all your entities have the id property playing the primary key role. Mostly this id is an Integer, but in a few cases it is a String. Many of your entities have the property name, e.g. User, or Client, or Organization. User and Client are people, so, you may describe their common properties (name, surname, birthDate, taxNumber, etc.) in the Person class. The name property must go into the abstract NamedEntity class, because Organization has this property too. But taxNumber is applicable for Clients only, so it must go into the Client class. However an Organization may also play the Client role, so the Client is not a Person, it is just an abstract client extended by ClientPerson and ClientOrganization classes. In such situation the ClientPerson must extend two classes: Person and Client. And similar the ClientOrganization must extend Organization and Client. So, the result hierarchy looks like the following:
However, in Java you have no multiple inheritance and interfaces are not working because the properties must be marked with JPA-annotations. Any case, if it would be even possible to work with multiple interfaces, you have to duplicate the code implementing the same properties and/or you have to aggregate single implementations and duplicate the code accessing these aggregated implementations. In other words, the aggregation of implementations is also code duplication.
I hope that's enough to ask you, would you really like to do all of this routine and repeatable work? Would you really like to write manually dozens of classes and define hundreds of properties, even if the getters/setters are being generated by your IDE? Would you like to think over the listener support and well organized hierarchies? Again, would you like to do just a lot of repeatable work?
I hope you need a pragmatic and optimal implementation of your task with a minimum of duplicated code and minimum of the routine and repeatable work from your side. If so, let me to ask you, do you really need to have a precise relational mapping of a business entity onto appropriate Java class? If no, let me to propose the solution implemented in this framework.
SolutionThe idea of the solution is based on meta-view of a system. Each entity is just an Entity, and each entity property is just a Property. So, everything you need is just the Property class to define any named properties, and the Entity class for defining any named entities with a set of child properties.
So, each Property has a name and a value. Of course it would be nice to extend the Property with a valueClass and defaultValue. Further extension may include a support for PropertyListener and any other features you would need.
Similar, each Entity has at least a name and a set of child Properties. Further you may extend it with an EntityListener and other things.
Of course, you may say, such solution is non-optimal, because each Property except the value must also store its name, its valueClass, defaultValue, etc. And the same is with the Entity – each Entity must know its name and probably something more. So, e.g. if we have just a thousand of different instances of the Ware entity, our Entity class stores the name “Ware” thousand times. Each of these thouthand instances will have, e.g., three properties (id, name and price). So, these three strings (“id”, “name” and “price”) will be stored each for 1000 times too. The same is for defaultValue, etc.
To avoid such situation we introduce the PropertyDescriptor and EntityDescriptor appropriately. These classes include any common information about the properties and entities appropriately (name, valueClass, defaultValue, etc.). So, each Property and Entity must know just about their descriptors. It is just a single reference per object. It is also possible to avoid duplication of descriptors themselves. Internal mechanism stores the descriptors in internal maps and if you create a new property (even for another entity) with the same common attributes, the existing descriptor will be used. All of this is done transparently for the end-developer (means for you).
ExampleThe package ru.rododin.dynamic_entity_engine.demo.unit contains a simple example implementation based on this idea. The example introduces a few Units defined by the UnitDefinition enumeration. Of course, it is very easy to develop another implementation where the units will be specified just using a .properties file or using an XML-configuration file.
I would propose to see the sources in the following order:
UnitDefinition UnitPropertyDefinition Unit Entity Property EntityDescriptor PropertyDescriptor AbstractEntity AbstractProperty StandardEntityDescriptor StandardPropertyDescriptor
The result class diagram looks like this:
Source CodeThe source code is accessible here.
The pure base idea is provided by this tag:
http://dynamic-entity-engine.googlecode.com/svn/tags/version-0.1.1-base-idea
The tag version-0.2.0-typical-listener provides now a typical implementation of PropertyListener and EntityListener support:
http://dynamic-entity-engine.googlecode.com/svn/tags/version-0.2.0-typical-listener
This implementation is not optimal, because we have here some of repeated code. For instance, see EntityListenerManager. As you may see, the methods entityXxx processing different kinds of events are mostly identical. Of course, it’s possible to provide a single implementation based on the reflection engine. In other words, we can find a required method by a passed string name and invoke it via reflection. However this way isn’t optimal too, because in a real system it may cause invocation of thousands listeners in such way. Currently I’m working over another alternative implementation for the event listeners (TODO 3). Idea of this implementation is the same as the base idea of this framework – the set of events and listeners is being defined and controlled via an enumeration.
The head branch contains working copy of the sources:
http://dynamic-entity-engine.googlecode.com/svn/trunk
The project is based on Maven and provides a standard POM-file. In addition there is an IntelliJ IDEA project (of course generated using the mvn ided:idea command, but with some post-settings done). I hope you will able to download the sources from SVN (see also: http://tortoisesvn.tigris.org), build and run it using Maven or IDEA.
TODOProvide a class diagram (reverse-engineered) for the engine – done. Add event listener support to the framework (PropertyListener and EntityListener) – typical implementation is completed in the version 0.2. Re-implement the listener support using the same idea as the base idea of the framework (meta-listener implementation) Add support for UnitRenderer and UnitController to the Unit entity specialization to show the extensibility of the idea. MAJOR TODO: Think and provide a concept of an ORM framework for the engine. Investigate how much Hibernate or Top-Link (or what's else) could be useful and/or applicable for the framework. The main thing to research is the finding a simplest and quickest possibility to map the engine Entityies and Propertyies onto DB tables and fields appropriately. [Less]