persistence.md 9.04 KB

BeuthBot persistence FAQ

Which technologies are used in the persistence layer?

  • A PostgreSQL database is running as a seperate docker container
  • Adminer is used as a web client for inspection and manipulation of the database; it is running in its own docker container
  • To enable the communication between java and the database, a database driver is required which is the postgres JDBC driver in our case
  • JPA is the specification which is used as interface for every interaction with the database using java
  • Hibernate implements JPA and is used as the persistence provider which does the actual object-relational mapping

Where can the configuration files be found?

How to interact with persisted entities?

Data Access Objects (DAOs) are designed as an encapsulation of JPA and Hibernate and should be used to create,retrieve, modify and delete entities. DAO interfaces provide at least CRUD functionality and DAO implementations use JPA to perform the necessary operations on the database.

The following snippet shows the base interface for all DAOs:

public interface GenericDAO<T extends Entity, ID extends Serializable> {

    T findById(ID id);

    List<T> findAll();

    T saveOrUpdate(T entity);

    void delete(T entity);
}

The following snippet shows a DAO interface for a specific entity having the basic CRUD functionality and optional extensions to that

public interface AppUserDAO extends GenericDAO<AppUser, Long> {
    AppUser createUser();
}

How can a DAO instance be used?

DAO implementations are stateless EJBs which can be used using dependency injection in a class that is managed by JBoss.

@Resource(lookup = "java:global/global/AppUserDAO")
private AppUserDAO userDAO;

How to create an entity?

Currently a DAO is responsible for creating instances of its context entity, e.g. AppUser newUser = userDAO.createUser(). Direct creation of an entity like AppUser newUser = new AppUser() was not possible at first, but could be used now, too. When the persistence layer was designed, direct references to the entity classes were impossible to use, because those classes were placed in the global module. This location changed to common because this solved some issues that could no be fixed otherwise, although this solution is less clean (see Why is the persistence layer located in common instead of global?).

public interface AppUserDAO extends GenericDAO<AppUser, Long> {

    AppUser createUser();
}

The entity can then be modified and updated in the database using the DAO instance.

AppUsers have generic attributes. How can they be used?

The Entity-Attribute-Value model (EAV) is a common pattern in relational database design which aims at structuring a database schema in rows instead of columns. Please do some research if you want to really understand it. The pattern is implemented and used in the AppUser entity to give developers the ability to define attributes at runtime. The advantage here is that attributes can be created and retrieved in Drools without changing the actual entity class. The disadvantage is that a developer cannot rely on an attribute being present and having the expected data type. The following snippet is a usage expample of the implemented pattern.

AppUser u = userDAO.createUser();

u.setProperty("test_property_string", "test_value");
u.setProperty("test_property_bool", true);
u.setProperty("test_property_double", 3.65);

userDAO.saveOrUpdate(u);

The shown syntax is achieved using custom json serialization as a convenience method. Instead of this, the EAV structure can be build manually, too. Please note that complex data types and lists are not supported using this syntax at the moment. To add support, the custom serializers and deserializers found in services/Common/.../persistence must be extended.

How does the implementation of the EAV pattern look like?

Domain model as UML class diagram

The important part here is that the GenericEntity can contain a recursive structure similar to the composite design pattern. This way primitive types are supported as well as lists and complex data types.

How to retrieve the generic attributes of an AppUser?

The following snippet shows the retrieval using the convenience method. Instead of this, the EAV structure can be queried an walked manually, too.

AppUser u = userDAO.findById(1);

String testValueString = u.getProperty("test_property_string", String.class);
boolean testValueBool = u.getProperty("test_property_bool", Boolean.class);
Double testValueDouble = u.getProperty("test_property_double", Double.class);

How does the convenience method for accessing the GenericEntity hierarchy actually work?

A trivial implementation providing access to the structure with getters and setters is provided, but it gives the responsibility of traversing this structure to the using component. To simplify this for developers, a mechanic using custom JSON serializers and deserializers has been implemented. When setting a generic attribute, the given value is serialized to JSON using a common serializer. The value as JSON is then given to the custom deserializers which can create a GenericEntity structure from this JSON. When retrieving the value later, the GenericAttribute hierarchy behind the given attribute key is serialized to JSON using the custom serializers as if it would be an unvariable implemented structure instead of the dynamic EAV model. This JSON is then given to a common deserializer which creates an instance of the desired type from it.

To understand the flow better, see the following example. A common serialization of a GenericEntity hierarchy could look like the following:

{
    "name": "user",
    "attributes": [
        {
            "name": "test_property_string",
            "values": [
                {
                    "valueAsLong": null,
                    "valueAsBool": null,
                    "valueAsDouble": null,
                    "valueAsString": "test_value",
                    "valueAsEntity": null,
                }
            ]
        },
        {
            "name": "test_property_bool",
            "values": [
                {
                    "valueAsLong": null,
                    "valueAsBool": true,
                    "valueAsDouble": null,
                    "valueAsString": null,
                    "valueAsEntity": null,
                }
            ]
        }
        {
            "name": "test_property_object",
            "values": [
                {
                    "valueAsLong": null,
                    "valueAsBool": true,
                    "valueAsDouble": null,
                    "valueAsString": null,
                    "valueAsEntity": {
                        "name": "FavoriteFood",
                        "attributes": [...]
                    }
                }
            ]
        }
    ]
}

The custom serializers instead have something like the following output:

{
    "test_property_string": "test_value",
    "test_property_bool": true,
    "test_property_object": {
        ...
    }
}

This result can be used as input for a common JSON deserializer which can create an instance of a desired type from it as long as the type has exactly the same attributes as the structure has in JSON.

Why is the persistence layer located in common instead of global?

Short story that might be useful to read: When implementing the persistence layer, the approach was to define entity interfaces and DAO interfaces in common and seperate implementations in global. From outside of the global module a developer should only be able to interact with the entities through their interfaces. Although this is the clean way which even compiled without problems, the entity instances could not be used all the time. In some cases a ClassCastException arose when trying to cast an entity instance to its interface. This should work fine normally. Research showed that each module in Wildfly 10 has its own class loader. That is why an instance created in a module which is not directly referenced can lead to problems. This can even happen when casting an object to its own class because the instance and the class have been loaded by different class loaders that cannot resolve types loaded by the other loader.

Because nobody was able to deal with this problem correctly, we decided to move the whole persistence layer implementation to common although this is clearly a design flaw.