Fake ServiceBuilder Entities

Introduction

So this is a concept that comes up from time to time on the forums - How do I return a non-database complex object from my ServiceBuilder-generated service layer?

The common answer is usually to bundle the class(es) defining the complex objects into a jar and deploy the jar to the global class loader.  This is a workable solution, but as with all global jars it can only be updated when the app container is shut down.  When organizations go this route, most of the time the simple global jar with model classes tends to grow into a monstrosity jar with the model classes, service classes for dealing with the model classes, etc.  They naturally grow to an unweildy size that require a lot of maintenance and deployment activity.  This leads to runtime issues where each consumer of the business logic classes in the global jar are actually instantiating their own instances in their own web applications so there's multiple runtime objects and no "shared" instance access.

So what starts as a seemingly simple solution can actually get bogged down over time.

An alternate solution that was initially outlined in this forum thread centers around what I call, for lack of a snazzier name, Fake Entities.

What Is a Fake Entity?

So what is a Fake Entity really?  Well it is exactly like a regular ServiceBuilder entity minus the backing database table.  Since it is like a ServiceBuilder entity, though, there's a lot of functionality you get:

  • Local interfaces for working with the entities.
  • Remote interfaces for accessing with regular web services.
  • With the remote interfaces you can layer in security checks to manage access.
  • Since the fake entities are managed by a ServiceBuilder implementation, you have one service providing plugin exposing a single access point for business logic so there can be data and logic sharing by all service consumers.
  • There is reduced resource requirements portal-wide because there is a single instance of the service provider classes.
  • It can be easy/natural to add and use the entities alongside the regular database-backed entities (some restrictions apply wink).
  • Updates to the entity and the service layer are deployed just like any other ServiceBuilder plugin is deployed.

Basically any benefit you can name for a database-backed ServiceBuilder entity usage applies to the fake entities.

So now you're sold on using Fake Entities, and you probably want to know how to do so.

Defining a DataSource

Yes, we still need a DataSource.  ServiceBuilder is dependent upon a DataSource definition for the entity and, if one isn't given, Liferay will use it's own DataSource (and create tables in the Liferay database).  As we don't want to create database tables, we need an alternate DataSource.

From the forum thread, Remis Lima Baima suggests using Spring's org.springframework.jdbc.datasource.LazyConnectionDataSourceProxy as the DataSource.  The LazyConnectionDataSourceProxy is a DataSource implementation that will not connect to a database until a javax.jdbc.Statement object is created.  For our fake entity purposes, as long as we do not attempt to do anything w/ the database side (i.e. the persistence layer or the finders, etc.) it would work.  Obviously, though, the risk is that some developer may try to use the data tier w/o knowing they're fake entities and you have to deal w/ all of the database exceptions that would be thrown as a result of missing tables.

You could protect yourself from this by using an HSQL database connection to back the LazyConnectionDataSourceProxy.  That way if entities do get created they would be in a throwaway database.  The problem here is that eventually the developers might start using them as database entities (since they would work as if they were real database entities), so you become dependent upon a database that is not intended to necessarily be a production database source.

I like the recommendation from my good friend Jack Bakker.  Basically he created a utility class to use to define a DataSource factory that always returns a null DataSource: 

package com.example;

import javax.sql.DataSource;

/**
 * class NoDataSource: A factory class to do nothing but return a null data source object.
 *
 * @author Jack Bakker
 */
public class NoDataSource {
	/**
	 * getDataSource: Supposed to return a DataSource.
	 * @return DataSource Always returns null.
	 */
	public static DataSource getDataSource() {	
		return null;
	}
}

I think the brilliance of this idea is that if a developer tries to access the database portion for an entity, it's going to generate a NullPointerException.  While not very ellegant, it will leave little doubt that the database layer is off-limits.

Defining the ext-spring.xml Beans

To use your datasource, you have to have some additional beans defined in the META-INF/ext-spring.xml file for your ServiceBuilder project:

 

<?xml version="1.0"?>

<beans xmlns="http://www.springframework.org/schema/beans"
	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd 
	                    http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">

	<bean id="noDataSource" class="com.example.NoDataSource" factory-method="getDataSource" />

</beans>

So we're defining our factory class with the factory method to create a DataSource instance.

Note: On using this in an EE SP 11 environment I got a NullPointerException from com.liferay.portal.jsonwebservice.JSONWebServiceRegistrator.  Apparently this defines a bean named noDataSource correctly for runtime but not available loading as a bean which leads to the NPE.  There's currently a fix in the pipeline to deal with the null bean reference in the registrator, but in the mean time you can actually solve this by creating a "NullDataSource" class that implements javax.sql.DataSource.  In your methods, just return nulls for everything and you're good.  The bean would then become <bean id="noDataSource" class="com.example.NullDataSource" /> and the NPE will go away.

Defining the Entities in service.xml

Next we'll add the entity definitions in our service.xml file:

 

<!DOCTYPE service-builder PUBLIC "-//Liferay//DTD Service Builder 6.2.0//EN" "http://www.liferay.com/dtd/liferay-service-builder_6_2_0.dtd">

<service-builder package-path="com.dnebinger.liferay.fake">
	<namespace>fake</namespace>
	
	<!-- Define our first fake entity... -->
	<entity name="Foo" uuid="true" local-service="true" remote-service="true" data-source="noDataSource">

		<!-- Even though we're a fake entity, we still need to have one column as a primary key. -->

		<column name="fooId" type="long" primary="true" />

		<!-- Now we can add any other columns to our entity that we want. -->

		<column name="field1" type="String" />
		<column name="field2" type="boolean" />
		<column name="field3" type="int" />
		<column name="field4" type="Date" />
		<column name="field5" type="String" />
	</entity>
	
	<!-- Define our second fake entity -->
	<entity name="Point" local-service="true" remote-service="true" data-source="noDataSource">
		<column name="x" type="double" primary="true" />
		<column name="y" type="double" primary="true" />
	</entity>
</service-builder>

The Foo entity is just a simple example of what you would do in your entity.  The Point entity represents a point on a cartesian plane.  We'll be using points to create a small library of geometric functions based on the Point.

Building Services

Before you can add methods to your implementation class, you need to build the services.  There's nothing special in our entities so building services in this case is a simple process.

It is during the building of services where you may encounter issues in your entity definitions.

One common error is leaving out a primary column.  Even though these entities will not be backed by a database, ServiceBuilder still requires that at least one column is identified as the primary column.  Doesn't matter which one(s) you use, but they will be part of the constructor for the instance.

Otherwise the same rules for database-backed entities enforced by ServiceBuilder are also apply to our fake entities.

Adding Business Methods

Building services gives us the initial service layer, although the default SB methods shouldn't be used (since we don't have a database) so they are not very helpful.  So let's add some methods to our new com.dnebinger.liferay.fake.service.impl.PointLocalServiceImpl class.

I hate exposing things outside of the service layer that I don't need to.  Since our Point has a compound key, the normal createPoint() method takes a PointPK instance.  Instead of requiring the service consumer to know about the PointPK class, I prefer to add a utility method that overloads the creation:

/**
 * createPoint: Utility constructor to hide the PointPK class.
 * @param x
 * @param y
 * @return Point The newly constructed point.
 */
public Point createPoint(double x, double y) {
	return createPoint(new PointPK(x,y));
}

Since these are fake entities, you might want to override the default methods to throw exceptions should some unknowing developer try to invoke the class:

/**
 * @see com.dnebinger.liferay.fake.service.base.PointLocalServiceBaseImpl#addPoint(com.dnebinger.liferay.fake.model.Point)
 */
@Override
public Point addPoint(Point point) throws SystemException {
	throw new SystemException("Points cannot be persisted.");
}

It's a lot of busywork to override all of the default methods, but it can save you some headaches in the future.

But we're really here to create some business classes.  I've added three simple methods to find the slope of a line, the distance of a line and calculate the area of a triangle formed by three points:

/**
 * findSlope: Finds the slope of a line for two points, m = (y1 - y2) / (x1 - x2).
 *
 * @param p1
 * @param p2
 * @return double The slope of the line.
 */
public double findSlope(final Point p1, final Point p2) {
	final double m = (p1.getY() - p2.getY()) / (p1.getX() - p2.getX());

	return m;
}

/**
 * findDistance: Finds the distance of a line formed by the two points, uses the distance formula, d = sqrt((x2 - x1)^2 + (y2 - y1)^2).
 * 
 * @param p1
 * @param p2
 * @return double The distance of the line.
 */
public double findDistance(final Point p1, final Point p2) {
	final double x2 = Math.pow((p2.getX() - p1.getX()), 2);
	final double y2 = Math.pow((p2.getY() - p1.getY()), 2);

	final double d = Math.sqrt(x2 + y2);

	return d;
}

/**
 * findArea: Finds the area of a triangle formed by the three points using Heron's formula:
 * 
 * @param p1
 * @param p2
 * @param p3
 * @return
 */
public double findArea(final Point p1, final Point p2, final Point p3) {
	// we actually need the lengths of the three sides...
	final double a = findDistance(p1, p2);
	final double b = findDistance(p2, p3);
	final double c = findDistance(p3, p1);

	// you also need the value for s
	final double s = 0.5 * (a + b + c);

	// and then you can calculate the area.
	final double area = Math.sqrt(s * (s - a) * (s - b) * (s - c));

	return area;
}

Not very useful or exciting, but it is just meant to demonstrate how we can build a library of functions around our fake entities.

We could also create a singleton instance of point that is available to all service consumers:

/**
 * cachedPoint: A shared point that will be visible across every service consumer.
 */
protected Point cachedPoint = null;
/**
 * getCachedPoint: Returns the cached point.
 * @return Point The cached point.
 */
public Point getCachedPoint() {
	return cachedPoint;
}
/**
 * setCachedPoint: Sets the cached point.
 * @param p
 */
public void setCachedPoint(final Point p) {
	cachedPoint = p;
}

Build Services Again

Since we've added methods to our PointLocalServiceImpl class, we have to build services again to add them to the service jar.

We can then use the service jar in other Liferay plugins to access the shared service tier.

Conclusion

So I've introduced fake entities and showed you how to create them, but you may still be wondering how they may apply to you.

The answer comes down to the real reason for ServiceBuilder's existence.  No, ServiceBuilder is not just a broken ORM or anything that you may have heard or thought on your own.

ServiceBuilder's raison d'etre is to provide a service layer that can be shared with many service consumers even across the web application container's class loader boundary.

I may get some pushback on this, but trust me it's the best way to think of ServiceBuilder.  It explains why it is a crippled ORM, why it is normal to include business logic in the service implementation, why it has built-in support for web services and JSON.  It explains why there is one plugin that is providing the service and multiple copies of the service jar floating in the environment with a thin API to access the shared service layer.

When you see ServiceBuilder in this light, it becomes clear that you will want to use entities for a lot more than just your database classes.

Have a set of web services that multiple portlet projects will need to invoke?  If you wrap them in a ServiceBuilder implementation, those portlets only need a service jar.  Your implementation classes can hide all of the details in creating and using the web service client.  For changes to the web service, well you just need to make a change to your service provider, the service consumers can remain unchanged (assuming you don't need some sort of API change).

Have a non-database resource that multiple portlet projects will need to read/write?  If you wrap them in a ServiceBuilder implementation, the service consumers can share access to the resource.  Service consumer A can write something to the resource and the other service consumers can read the value back.  The service provider can choose to cache the changes (if updating the resource is a time-intensive process) and since the service consumers are pulling from the service provider the cached value(s) can be returned.

Building a complex library of business logic to be shared with multiple plugins?  If you wrap it in a ServiceBuilder implementation, you get all of the benefits of using a shared implementation w/o runtime implications of multiple instanciated objects.

Have a Hibernate or JPA data access layer you need to use in multiple plugins?  If you try to copy the code to the separate plugins, they all need their own database connections, their own ehcaches, etc.  If you wrap this in a ServiceBuilder implementation, the service provider becomes a proxy to your data access layer implementation.  Service consumers leverage the service jar to access the service provider; the service provider is the only plugin that requires the database connection, the ehcache, etc.  Since the service consumers are all going through the same instance, issues with stale caches and dirty reads/writes disappear.

All of these things become possible in ServiceBuilder with the introduction of the Fake Entity.

Blogs
I like the fake entity with the fake datasource - I've been using empty entities, but this means that model beans have to be totally hand-written, adding more work. This is a good alternative. Thanks for the pointer
Empty entities are great for layering in business logic, but you're stuck using primitives, their Object counterparts, Collections or Entities.

The Fake Entity lets you add your own custom classes that are not database-dependent.
Loved it! Thank you for collating and adding your own expertise .. all at one place.
can't get easier than this.

Thank you
Should be a wiki or tutorial in dev.liferay.com :-)
Not sure if this applies to the use case in which you need to add a Service layer using Service Builder around an existing DB used by a group of Liferay portlets. I have been waiting for someone to crack the reverse engineer a DB into a service.xml, similar to Dali tools reverse engineering a JPA persistence.xml from database tables.
can v use composition in fake entity
There's no magic to fake entities. They have the same features and constraints as regular entities. Contained collections within an entity typically give SB problems because there's no easy way to manage crossing the class loader boundary with those, so you'll frequently get the operation not supported exception.
Thank you, David, for this entry blog. I've implemented two Fake Entities with 6.2 EE using the persistence layer for caching purposes, and they're working perfectly.
As you said, I needed to implement the "NullDataSource" fix you talked about.
Brilliant. Thanks David.
Can you please describe more how we can code NullDataSource.
[...] Olaf Kock: The fake entity concept means that you're building an empty entity, with no properties. This will not create a table in the database, just make the service available. No, the "fake... [...] Read More

Can we follow the same in Liferay 7. when I am trying to create fake service in LR7 it's throwing Null pointer exception while accessing the service from portlet. 

David covered this in the latest Radio Liferay Episode 62 http://radioliferay.com/episode/62 . In short: A classic OSGi Declarative service suffices if you don't need any entity or other ServiceBuilder related handling.

Hi Olaf,

 

Thankyou for your updates. I need to create a finder with custom sql joins,  that's the reason looking for fake service builder. Can we implement multiple sql joins with OSGI declarative services?

That doesn't call for a fake entity. If you are returning the entity or a list of the entity, you can build custom SQL or a DQ to handle the join and Hibernate/SB will handle the rest of the marshaling effort.

Thankyou David!

 

I followed this documentation https://help.liferay.com/hc/en-us/articles/360017882012-Custom-SQL#step-1-specify-your-custom-sql to build custom SQL and it's working fine, only the table is already exist. But actually we don't have any custom table created in our application. We need to join JournalArticle, AssetEntry, AssetCategories and AssetEntries_Assetcategories tables to make a join SQL.

 

I am looking forward to make custom SQL without creating table in database.

Hi, Kalai Arasan! What you mean is you want to throw variable with different entity like in spring boot using nativeQuery, right? For this, you can refer to this article: http://liferayiseasy3.blogspot.com/2015/02/custom-sql-with-two-table_17.html But, if you refer to that articel, you cannot load parameter via api/jsonws (you should load data directly using ajax). because you throw object entity that never declare. I often use create view either via database or table.sql (via liferay) and call them by creating new serviceBuilder and connect the view by connecting to external dataSource (to connect to external dataSource, you can refer to https://help.liferay.com/hc/en-us/articles/360018160911-Connecting-Service-Builder-to-External-Data-Sources).

I don't know if it is the best way or not but it works very well for me. Hope it may helps :)