Fórumok

ServiceBuilder and JPA

Martin Straus, módosítva 11 év-val korábban

ServiceBuilder and JPA

New Member Bejegyzések: 19 Csatlakozás dátuma: 2012.07.06. Legújabb bejegyzések
I've been working with Liferay for a few months now, and I need to ask this: why does Liferay use its own, propietary persistence framework (ServiceBuilder) instead of something standard, such as JPA?

I understand that under the hood, ServiceBuilder uses Hibernate, which means that migrating wouldn't be difficult at all, so I don't get it... why reinvent the wheel? I find myself mapping Liferay tables to my own implementations of JPA entities just to avoid ServiceBuilder. Which, frankly, renders most of the ServiceBuilder stack irrelevant.

Could someone please enlighten me?
thumbnail
David H Nebinger, módosítva 11 év-val korábban

RE: ServiceBuilder and JPA

Liferay Legend Bejegyzések: 14916 Csatlakozás dátuma: 2006.09.02. Legújabb bejegyzések
See my comment in the post here.

Basically it's architectural... When you're creating a stand alone web application, you can put all of your hibernate/jpa access in there and it works. There are no interdependencies because it is, well, a standalone application.

In the portal world, we don't think like that. We don't create one portlet that has the entire web application managed inside of it. Instead we create separate portlets, where each portlet (should) do one thing (or a small list of things) and do them really well. These portlets tend to have some dependencies, such as accessing the same data and needing access to shared logic.

SB allows for this by creating the underlying framework of data access suitable for use in a portal environment. It solves the problem of multiple individual portlets needing to access the same data and the shared logic. It makes accessing this functionality across the web application class loader boundary possible (note that separate plugin wars have separate class loaders and therefore it is normally difficult to share data/code across that boundary w/o a lot of work on the developers part). Since there's a single plugin actually doing the data access, caching can be used because the single writer is aware of database changes (whereas if you had multiple independent writers, caching becomes a problem because one instance could have stale data).

SB has a number of downsides, mostly related to the partial support of a typical ORM implementation (limited one to many and no many to many support), but the downsides are easily accounted for in code and the benefits make it worth it.
Martin Straus, módosítva 11 év-val korábban

RE: ServiceBuilder and JPA

New Member Bejegyzések: 19 Csatlakozás dátuma: 2012.07.06. Legújabb bejegyzések
The issue is not a matter of architecture but implementation. They could have used EJB for the service layer, and JPA for the persistence layer. They could have provided a way to cleanly hook your own implementation of the service layer, istead of forcing the developer to use the code generated by the service builder. Does the service builder offer any advantage to the developer, besides code generation? Most of that code would not need to be generated in the first place, if using other technologies. The service builder does not provide a clean way to access a service created in one project from another. Or should the SDK be blamed for this?

It seems to me that at some point it was decided to build this custom service and persistence layers, and once down that road, the cost of doing something a little better becomes huge. It might require to reimplement the whole system and break compatibility of every custom portlet out in the market.
thumbnail
David H Nebinger, módosítva 11 év-val korábban

RE: ServiceBuilder and JPA

Liferay Legend Bejegyzések: 14916 Csatlakozás dátuma: 2006.09.02. Legújabb bejegyzések
Well, first off Liferay targets a servlet container, and not a full-blown j2ee container (which EJB usage would require). Liferay started back in the day when there was no JPA and you had to deal with CMP vs BMP and the whole EJB thing was a nightmare. At the same time Spring and Hibernate were both coming of age and demonstrated clear benefits over J2EE...

Now, however, they've got a big legacy codebase all built off of spring/hibernate. To move to JPA shouldn't be too hard, but it really wouldn't change anything...

Here's the deal. What SB brings to the table is the ability to expose the data access layer across the class loader boundary. In a servlet/j2ee world, this is not really a problem. Each app has it's own class loader boundary and apps are expected to never cross the boundary into another app's class loader.

A portal is a horse of a different color. In a portal, the class loader boundary is a real problem. Liferay has it's web app, deployed as ROOT.war, and as a portal developer you're going to create one or more plugins that get deployed as separate WAR files.

Because of the class loader boundary, your plugin would normally not have any means of invoking Liferay's data access layer to have access to the Liferay data. EJB and JPA don't help in this situtation because they, too, are in a separate class loader and not directly accessible.

What SB brings to the table is the ability to create data access layers that can cross the class loader boundary. It's the foundation of what SB supplies, and it allows your plugins to access Liferay's data access layer and also expose your own data access layer for other plugins to consume.

You can't look at SB and say it's a crappy ORM (which it is) or a confusing code generation tool (which it is), you have to see it for the value it brings to the table, the shared data access across the class loader boundary (which it does).

Many folks, first time out of the gate, look at SB and think it's a piece of junk and they can create a better data access layer with native hibernate or JPA. And it is true. We can all do better than what SB gives us.

It's not until you get into a situation where you have, say, 20 different portlets all deployed as separate war files and each with their own Hib/JPA stuff in play and your trying to figure out things such as why you have so many connections going to the database or why one portlet isn't seeing updates made by another portlet (because you might not understand what Hib/JPA is actually caching for you), that you realize the shortcomings in a Hib/JPA solution...

SB was designed and architected to solve these problems. These are the problems that you have in a portal environment. They were never setting out to create the world's greatest ORM tool or code generation tool, Liferay set out to solve this really difficult architectural problem that the portal faces.

And it's important to point out that they eat their own dog food. They use SB for all of their data access, both within the ROOT web applications and through the other plugins that they provide separately.

the cost of doing something a little better becomes huge


There's the rub. ORMs have evolved, Hib/JPA have evolved, certainly. But there is nothing better to resolve the specific problems that SB was designed to solve: crossing the web app class loader boundary.

I am a big SB proponent for the Liferay platform, and I do really push folks to use it. I do so because I am a Liferay architect and developer (this is my day job), and I totally understand the problems that SB solves. I don't take this position because I'm some Liferay mouthpiece or something, I take this position because I fully understand the architecture and implementation of Liferay, and I have the experience to know, for a fact, that SB is the right tool for the job for portal development.
Martin Straus, módosítva 11 év-val korábban

RE: ServiceBuilder and JPA

New Member Bejegyzések: 19 Csatlakozás dátuma: 2012.07.06. Legújabb bejegyzések
Well, I think I hurt your feelings somehow. My appologies if that's the case.

Now, what you say about the classloader boundary makes a little more sense than your previous high-level explanation about "sharing services". But bear with me for a second...

What I critize is the "builder" part in "service builder". And I don't see how it solves the issue you mention. It just generates classes, which still can't be shared between portlets deployed in different wars. Or perhaps you can, and I'm missing something fundamentally spectacular about SB. Could you point me towards some piece of technical documentation about this?

This is the doubt that moved me to post this question; not a desire to troll the Liferay team.
J B, módosítva 11 év-val korábban

RE: ServiceBuilder and JPA

Junior Member Bejegyzések: 37 Csatlakozás dátuma: 2012.04.05. Legújabb bejegyzések
David will tell you put the service jar in the app server lib/ext directory and then you can call the Impl methods of the service builder jar across several portlets all packaged in different war / plugin files.

I think the whole service builder architecture is Spring ORM; not sure if there is a persistence.xml as part of the generated code.

There is no reason one could not make a full spring hibernate jpa app and put the jar in the lib/ext and do the same thing service builder does?

Am I wrong?
J B, módosítva 11 év-val korábban

RE: ServiceBuilder and JPA

Junior Member Bejegyzések: 37 Csatlakozás dátuma: 2012.04.05. Legújabb bejegyzések
David...you don't need a full j2ee container (which EJB usage would require) to do JPA
Martin Straus, módosítva 11 év-val korábban

RE: ServiceBuilder and JPA

New Member Bejegyzések: 19 Csatlakozás dátuma: 2012.07.06. Legújabb bejegyzések
David will tell you put the service jar in the app server lib/ext directory and then you can call the Impl methods of the service builder jar across several portlets all packaged in different war / plugin files.


While this would work (in theory, at least), it's not really fixing the classloading boundary problem the presented, is it? We don't need SB to do that. And it would also work for a JPA persistence unit...
thumbnail
David H Nebinger, módosítva 11 év-val korábban

RE: ServiceBuilder and JPA

Liferay Legend Bejegyzések: 14916 Csatlakozás dátuma: 2006.09.02. Legújabb bejegyzések
Martin Straus:
Well, I think I hurt your feelings somehow. My appologies if that's the case.


Nope, I've got a pretty thick skin. I've been having this argument a lot, and sometimes it's just tough selling people on the benefits of SB...

Now, what you say about the classloader boundary makes a little more sense than your previous high-level explanation about "sharing services". But bear with me for a second...

What I critize is the "builder" part in "service builder". And I don't see how it solves the issue you mention. It just generates classes, which still can't be shared between portlets deployed in different wars. Or perhaps you can, and I'm missing something fundamentally spectacular about SB. Could you point me towards some piece of technical documentation about this?


The class loader proxying that SB generates is a big part of the value equation for SB over any other ORM package...

When you have two different web apps (i.e. two different portlets deployed as separate wars), the impact of the class loader is little understood (there's lots of posts in the forum that result from not understanding the class loader issues).

The impact is best represented in an example. Say you have a jar, example.jar, and this jar has a class com.example.Class. The exact same jar can be put into the WEB-INF/lib of each of the wars. Even though the class is exactly the same from exactly the same jar, at runtime they are (for all intents and purposes) completely different.

Getting across the class loader boundary is done basically by using reflection. Using reflection, you can invoke methods on any object, even one loaded by a different class loader. The challenge, however, is the passing of data. Simple java primitives (and their object counterparts) can be directly used as they are always loaded by the JVM's class loader and have wide scope. But any java object (a pojo for example), cannot. The instance of Class created in the one web app cannot be passed as-is via reflection to the object that's been loaded in the other web app's class loader.

The only way to cross this kind of boundary is via serialization. SB actually generates code that lives in the service jar to serialize the instance (using java serialization) and pass the serialized version to the other class loader, where it is deserialized and used within the other class loader.

From the SB side, it's actually doing a lot of work. A typical sequence is:

1. Entity e = EntityLocalServiceUtil.createEntity(primaryKey);
2. <populate entity w/ values>.
3. EntityLocalServiceUtil.addEntity(e);

The actual steps behind this are:

1. EntityLocalServiceUtil.createEntity() will serialize the primary key and use a bunch of reflection magic to invoke the createEntity() method defined in the EntityLocalService classes in the service providing plugin.
2. The plugin creates an implementation class and sets the primary key.
3. The implementation is serialized and passed back to the EntityLocalServiceUtil side.
4. The EntityLocalServiceUtil side will deserialize into a locally available instance and is returned.
5. The <populate entity w/ values> sets values in the local instance within the local class loader.
6. The EntityLocalServiceUtil.addEntity() method serializes the local instance, invokes the service side code w/ the serialized version.
7. The service side deserializes the instance, passes to the service side's addEntity() method, result is the updated Entity which is deserialized and handed back.
8. The EntityLocalServiceUtil.addEntity() method deserializes the value given back and returns the instance to the local method.

Serialization is actually one of the magical hidden parts of SB. Every entity, every collection, whatever is passed as an argument or returned as a result, all of these things require serialization to cross the class loader boundary.

So just by leveraging SB and the generated service jar, you get automatic serialization support and a bridge across the class loader boundary.

What I critize is the "builder" part in "service builder".


Well, it is actually a 'builder' and not just a generator, although depending upon usage, it can just seem that way...

Basically it goes something like this... You define an entity in your service.xml file and set the local-service="true" and remote-service="true", then run SB. What you get out of this is:

* A separate service jar that has the shared code for other plugins.
* Support for local access using the XxxLocalServiceUtil classes.
* Support for remote access (web services) to your entity.
* Full support for crossing the class loader boundary to access your entity, plus additional methods that you add to XxxLocalServiceImpl classes.
* Full CRUD support for your entity in the database.
* Automagically creates the DDL to create the database table(s) if they don't exist. Deploy the plugin and your table(s) will be created for you.
* Ability to define custom finders in the entity definition, and SB generates the code to support the finders.
* DynamicQuery access to construct queries in a java DSL without knowing a lick of SQL (although understanding the relational data model is necessary).

So one entity definition gives you all of this functionality, and you really don't have to write a line of java code to get a basic SB project up w/ full CRUD access, web services, and class loader boundary transparency.

The service jar that is built by SB is provided to the plugins that need to access the entities. You have two options for sharing this. The first is to move the service jar to the global lib directory (tomcat's lib/ext directory for example), but this has drawbacks. First, you cannot do a redeployment if the app server is running as the current jar will be open, so a restart is necessary to do a redeploy. Really the only time you should go this route is if you need access to the service layer from a hook or ext plugin (you're going to use the service from within the portal itself). The second option, my preferred option, is to add a "required deployment context" entry in the liferay-plugin-package.properties file for the plugin that will be consuming the service. The Liferay IDE will automagically copy the service jar to the local plugins' docroot/WEB-INF/lib directory (does this every time you rebuild the services within the IDE). Since it's not a global lib deployment, you can still leverage the hot deployment process.

The other portion of the code generated by SB remains in the project hosting the service. Here you primarily will do your work in the XxxLocalServiceImpl files to add methods, expose finders, implement some business logic, etc. When you're done making changes here, you re-run SB and the changes will get propagated to the service jar.

Although the focus of SB is on a DB access layer, you can also use it to share non-db code. Define an entity w/o columns and add your methods to the XxxLocalServiceImpl, and these functions will be available in the service jar, so other plugins can call the methods as though they were local, yet the functions are shared by all plugins in the system.

I know this was a lengthy tirade on SB, but it is necessary info to help explain the value of SB and why you should use it. Hopefully it helps...
Martin Straus, módosítva 11 év-val korábban

RE: ServiceBuilder and JPA

New Member Bejegyzések: 19 Csatlakozás dátuma: 2012.07.06. Legújabb bejegyzések
Getting across the class loader boundary is done basically by using reflection. Using reflection, you can invoke methods on any object, even one loaded by a different class loader. The challenge, however, is the passing of data. Simple java primitives (and their object counterparts) can be directly used as they are always loaded by the JVM's class loader and have wide scope. But any java object (a pojo for example), cannot. The instance of Class created in the one web app cannot be passed as-is via reflection to the object that's been loaded in the other web app's class loader.

The only way to cross this kind of boundary is via serialization. SB actually generates code that lives in the service jar to serialize the instance (using java serialization) and pass the serialized version to the other class loader, where it is deserialized and used within the other class loader.


I'm aware of the inner workings of classloading. Serialization is easy; in fact, native Java libraries accomplish that. But if you have two classloaders - A and B - and you serialize an object from A to B, you need a method to transmit the serialized state; RMI, for instance. Plus, B must somehow be able to load the serialized class' bytecode (that's exactly the function a classloader has)... How does SB solve that?

I appreciate the rest of your explanation, but all of that more or less describes modern ORM technologies, so I stand by my original assestment about the SB's added value in that regard.
thumbnail
David H Nebinger, módosítva 11 év-val korábban

RE: ServiceBuilder and JPA

Liferay Legend Bejegyzések: 14916 Csatlakozás dátuma: 2006.09.02. Legújabb bejegyzések
Martin Straus:
I'm aware of the inner workings of classloading. Serialization is easy; in fact, native Java libraries accomplish that. But if you have two classloaders - A and B - and you serialize an object from A to B, you need a method to transmit the serialized state; RMI, for instance. Plus, B must somehow be able to load the serialized class' bytecode (that's exactly the function a classloader has)... How does SB solve that?


SB code relies heavily on reflection. So classloader A is the plugin providing the service, and classloader B is the plugin consuming the service. So B calls EntityLocalServiceUtil.updateEntity(Entity myValue). The really low level details are:

1. B gets A's class loader instance and uses it to create an instance of Entity relative to A's class loader.
2. B uses reflection to call all the setter methods on A's entity, passing in the member values from B's myValue.
3. B gets A's object that implements the EntityLocalService and gets a reference to the method to invoke.
4. B invokes the method in A's class loader using the Entity it just populated in A's class loader.
5. A does it's thing, and it returns a result object that is valid only for A's class loader.
6. B creates a response in it's class loader that matches the expected return type.
7. B uses reflection to call all the getter methods on A's entity to copy values to the instance in B's class loader.
8. B returns the value.

This serialization gets even more detailed when you have, say, a collection of Entity (where each instance of the collection goes through the same process) or an entity which contains child entities (the children must go through the same path).

It's not really serialization/deserialization, but it is an effective way to view how the process actually works.

I appreciate the rest of your explanation, but all of that more or less describes modern ORM technologies, so I stand by my original assestment about the SB's added value in that regard.


ORMs like Hibernate and JPA handle object marshalling, but not this kind of serialization to cross class loader boundaries.

I'm sure that, in the end, I'm not going to be able to convince you on the benefits of using SB Martin. In the end, you're going to draw your own conclusion and run off and implement your data access layer however you want. And really that's fine, Liferay will not block you from doing that.

I would say, however, that Liferay has about 10 years invested in portal development and yet they base all of their data access on SB, and it's a project on a much bigger scale than what you or I work on. Surely that should merit at least some confidence that they know what they're doing, at least in the portal space...
Martin Straus, módosítva 11 év-val korábban

RE: ServiceBuilder and JPA

New Member Bejegyzések: 19 Csatlakozás dátuma: 2012.07.06. Legújabb bejegyzések
This bit is the tricky one:

1. B gets A's class loader instance and uses it to create an instance of Entity relative to A's class loader.


This would required that both classloaders, A and B, have access to the bytecode of the instance's type. I'm guessing these are the options:
  • The class must be present in the WEB-INF/[classes/lib] directory of both portlets
  • The class must be in a parent classloader (the system's classloader, for instance).
  • SB creates a child classloader on the fly for each portlet, getting and loads the classfile wither directly from the filesystem, or a more sophisticated method.
,

The rest seems to be "simple" deep copy of properties.

I'm sure that, in the end, I'm not going to be able to convince you on the benefits of using SB Martin. In the end, you're going to draw your own conclusion and run off and implement your data access layer however you want. And really that's fine, Liferay will not block you from doing that.


I know that. However, I'm an architect myself and I need to weigh multiple variables when deciding which guidelines to follow in my projects. Mantainability, testability, learning curve, to name a few. I won't use a new tool (new for me, that is) such as SB unless I have a very clear reason to do so. Thus, my question about what does SB really brings to the table.

Thank you very much for the information you gave me... and for your patience, of course!
thumbnail
David H Nebinger, módosítva 11 év-val korábban

RE: ServiceBuilder and JPA

Liferay Legend Bejegyzések: 14916 Csatlakozás dátuma: 2006.09.02. Legújabb bejegyzések
Martin Straus:
This bit is the tricky one:

1. B gets A's class loader instance and uses it to create an instance of Entity relative to A's class loader.


This would required that both classloaders, A and B, have access to the bytecode of the instance's type. I'm guessing these are the options:
  • The class must be present in the WEB-INF/[classes/lib] directory of both portlets
  • The class must be in a parent classloader (the system's classloader, for instance).
  • SB creates a child classloader on the fly for each portlet, getting and loads the classfile wither directly from the filesystem, or a more sophisticated method.
,

The rest seems to be "simple" deep copy of properties.


It's normally the first item. The service jar contains the interface definition that the model classes implement. The interface is shared between A & B, although the implementation classes are not.

The second is used when the service needs to be shared with the portal. In this use case, the service jar must be copied to the global lib dir. You'll have to go this route when you need to access the service in a Hook or EXT plugin. I would argue that if it comes to this, you're probably better of using your own portlet rather than patching one of Liferay's as it may impact your ability to handle future upgrades.

I'm sure that, in the end, I'm not going to be able to convince you on the benefits of using SB Martin. In the end, you're going to draw your own conclusion and run off and implement your data access layer however you want. And really that's fine, Liferay will not block you from doing that.


I know that. However, I'm an architect myself and I need to weigh multiple variables when deciding which guidelines to follow in my projects. Mantainability, testability, learning curve, to name a few. I won't use a new tool (new for me, that is) such as SB unless I have a very clear reason to do so. Thus, my question about what does SB really brings to the table.

Thank you very much for the information you gave me... and for your patience, of course!


I was much in the same boat as you when I started on the Liferay path. I too didn't see the immediate benefits that SB brought to the table. My first few portlets were all based on Hibernate. Had a nice, clean hibernate/spring implementation that I had bundled into a jar so I could share the jar and the mappings between the separate portlet projects (they were separate wars but all used the same set of tables). From an architectural standpoint, it was clean and tidy.

Then came the implementation stuff that I didn't plan on... EhCache was caching query results, but with multiple portlets doing db updates the caches were stale. So I started by disabling the caching (negative performance impact but solved the stale cache problem). Then there was the resource consumption issue... Each portlet was using a connection pool, but depending upon how many users were accessing the system simultaneously my DB connection count would skyrocket and Oracle would stop accepting connections, so I had to throttle back the sizes of my connection pools (also negatively impacting performance, but solved the connection overload issue). Eventually I got to the point that I merged all of the separate portlets into one big project that was deployed as a single war file (this allowed the use of the cache and the larger single connection pool, but the project was tough on developers because they had to spend time merging code into one project the whole team was working on).

That's when I finally bit the bullet and learned about what SB was and how, if I had taken it up from the beginning, I could have avoided all of these issues....

So now I ring the SB bell anytime someone posts about Hibernate and/or JPA because they typically are not doing the kinds of analysis you're doing to be able to make educated decisions about when to use SB vs one of the others, they just are looking for a quick answer so they can wrap up the project they're on and move to the next one... emoticon
thumbnail
Mika Koivisto, módosítva 11 év-val korábban

RE: ServiceBuilder and JPA

Liferay Legend Bejegyzések: 1519 Csatlakozás dátuma: 2006.08.07. Legújabb bejegyzések
You don't have to use ServiceBuilder in your own portlets you can use JPA, Hibernate, Ibatis, insert you favorite persistence framework. ServiceBuilder was originally created to handle all the boiler plate stuff with EJBs but now a days it does lot more like handle all the complexities of sharing services across classloaders. There's certain restrictions to prevent people from getting into trouble with things like Lazy loading, open session in view etc. It supports the pattern we use in the product and it's not a general purpose persistence framework heck it's not even a persistence framework it's code generator. The persistence framework we use is Hibernate.