Liferay DXP - Calling Services inside ModelImpl

Dominik Ratzow, modified 6 Years ago. New Member Posts: 10 Join Date: 3/11/14 Recent Posts
Hi Community,

I tried to figure out how to call a localService inside a modelImpl. This will prevent to write duplicated code.

For example with 2 custom entitites:

Customter
--------------
customerId
customerName

Order
--------------
orderId
orderDescription
organizationId
customerId


In my OrderModelImpl I want to create a new attribute called customer of type Customer.

The getter should look like:

public Customer getCustomer() {
    if (customer == null) {
        customer = CustomerLocalServiceUtil.fetchCustomer(getCustomerId());
    }
    return customer;
}

It does not work (stacktrace attached). If I do the exact same thing with the organization it is working:

public Organization getOrganization() {
    if (organization == null) {
        organization = OrganizationLocalServiceUtil.fetchOrganization(getOrganizationId());
    }
    return organization;
}

Can anyone explain me why this is not working with custom entitites but for Liferay default entitites?
And what is the best practice to achieve my intention.

Thank you and best regards,
Dominik
thumbnail
Olaf Kock, modified 6 Years ago. Liferay Legend Posts: 6441 Join Date: 9/23/08 Recent Posts
Dominik Ratzow:

Hi Community,

I tried to figure out how to call a localService inside a modelImpl. This will prevent to write duplicated code.
...
Can anyone explain me why this is not working with custom entitites but for Liferay default entitites?
And what is the best practice to achieve my intention.

You're within the OSGi runtime and shouldn't continue to use the static methods from *LocalServiceUtil classes: They're legacy, and will work around proper runtime resolution and dependencies of services.

By implementing through this method, you'll not be guaranteed that the service is available to you. And indeed: The exception that you see is a NullpointerException.

Making the Model dependent on external services is not a proper abstraction: Models are for storage, not for actively reaching out to the environment - that's what the services are for. I'd avoid this implementation at all cost - and definitely avoid the *Util call when you're in an OSGi bundle.

This means that you're either bound to introduce duplicated code (buh), implement the customer fetching in your OrderLocalService, when the Order is first introduced (assuming that getting the customer is an operation that's done often anyway), or implement resolution of the customer through yet another indirection that gets injected by the service.

Note that this on-demand fetching of a customer is a prime way to introduce complexity and processing time: If you fetch 100 orders with one statement, and each order fetches its customer with 1 extra statement, you might end up executing 101 statements against the database (or the cache). Thus, fetching 100 customers together with the 100 orders is preferable and might happen in 1 or 2 statements.

Avoid the number of statements to be directly dependent on the number of fetched objects.
thumbnail
David H Nebinger, modified 6 Years ago. Liferay Legend Posts: 14933 Join Date: 9/2/06 Recent Posts
Actually I kind of disagree with Olaf in this case...

The static util classes are definitely "legacy", but they also serve in this case where your model instances are not going to have access to any of the services for this kind of "lazy lookup".

Your NPE does appear to be related to the CustomerLocalServiceUtil not actually having the CustomerLocalService available via the service tracker.

There's lots of reasons this can happen, such as:

  • The local service is, itself, not yet loaded or exposed, could be a code issue or something preventing the service from being available.
  • The lookup for the local service by the local service util's service tracker is failing for some reason.


  • The one thing you didn't mention was what version of Liferay you're using. You might actually be using a version of Liferay w/ a bug for all we know.

    All of this being said, you absolutely should ask yourself if you really want to do something like this.

    Sure a lazy lookup of the Customer object will seem to make sense, you can pull it and keep it with the order, you don't have multiple retrieves, etc.

    There are other problems that you have to consider:

  • Once you cache the Customer instance, it can be stale since it will not be updated if a Customer data is changed or deleted.
  • Holding an instance may pose garbage collection issues. You're holding a reference to an object that might otherwise have been GC'd after use but, because of this additional reference, it may not be able to be GC'd.
  • When there are massive parent-child relationship counts, there can be a lot of these multiple references held for no reason. In your case, how do things feel if you are holding 100 orders and each order has a reference to the same Customer object?


  • Often times you end up over-thinking an optimization that really isn't necessary at all.

    It is perfectly fine for your display code to manually retrieve the Customer instance in the display logic. Liferay uses EhCache to cache objects fetched from the DB, so even the case with 100 orders fetching the Customer 100 times, it really would only be a fetch from the DB once and then feed from the cache 99 other times.

    And, since Liferay is tracking changes to entities across the cluster, if someone changes Customer along the way, Customer will be dumped from all of the caches and a pull of the same ID for your order display will be a cache miss and the updated entity will be retrieved from the DB, thus dealing with the stale Customer record issue.

    My recommendation: Just don't do it. Unless you have actual performance and load test results which show your way is better than just retrieving using the API, 99% of the time your code is going to have bugs and will not offer any measurable performance improvement at all.

    KISS and everything will just work.
    Dominik Ratzow, modified 6 Years ago. New Member Posts: 10 Join Date: 3/11/14 Recent Posts
    Hi Olaf,

    thank you for your response. " implement the customer fetching in your OrderLocalService, when the Order is first introduced (assuming that getting the customer is an operation that's done often anyway)" thats the way to go. I will override the fetch method of the OrderLocalService and I will fill in the customer object through the service.

    Note that this on-demand fetching of a customer is a prime way to introduce complexity and processing time: If you fetch 100 orders with one statement, and each order fetches its customer with 1 extra statement, you might end up executing 101 statements against the database (or the cache). Thus, fetching 100 customers together with the 100 orders is preferable and might happen in 1 or 2 statements.

    This statement is very interesting. Can you explain this a bit more? You example is great. We have 100 Orders with 100 different customers. I will output the orders in a HTML table with structure:

    orderId                 orderDescription                 customerName

    I will get the 100 Orders in one query. Ok so far. I will loop the orders and I have to get for each order the customer object through the service. In total this will cost me 200 queries. Am I wrong? Is there a better way? How to achieve " fetching 100 customers together with the 100 orders"?

    Thank you,
    Dominik
    thumbnail
    Olaf Kock, modified 6 Years ago. Liferay Legend Posts: 6441 Join Date: 9/23/08 Recent Posts
    Dominik Ratzow:

    This statement is very interesting. Can you explain this a bit more? You example is great. We have 100 Orders with 100 different customers. I will output the orders in a HTML table with structure:

    orderId                 orderDescription                 customerName

    I will get the 100 Orders in one query. Ok so far. I will loop the orders and I have to get for each order the customer object through the service. In total this will cost me 200 queries. Am I wrong? Is there a better way? How to achieve " fetching 100 customers together with the 100 orders"?
    If you fetch 100 orders in 1 query, and then 100 customers in 100 queries, you end up with 101 queries, not 200. But the main point is that the number of queries depends on the number of records retrieved - something that might not be detected in testing with a handful of objects, but be really detrimental in production systems once you deal with enough data to become painful and hard to reproduce in test systems.

    I can't tell what the ideal architecture is, just provide some alternatives:

    * You might not need the full entity objects to "just" show partial information
    * You might want to implement paging, so that you only show (for example) 20 objects at once. Then determine if 21 queries are prohibitive or not. These are limited by the paging size, not by the number of the retrieved objects
    * I'll admit: I'm not sure if ServiceBuilder is able to pre-populate the customer objects out of the box. But you could do so by utilizing another query fetching all referenced customers (at least into cache) by utilizing a dynamic query on Customer providing it with all referenced customerIDs (resulting in the single query - pseudo-sql - "SELECT ... from Customer WHERE customerID IN ....."). This way you'll end up with 2 queries: One on Order, one on Customer. No matter how many objects you fetch.
    thumbnail
    Amos Fong, modified 6 Years ago. Liferay Legend Posts: 2047 Join Date: 10/7/08 Recent Posts
    I can add one more alternative for displaying in table:
    * Index your orders with customer name as a field. Then you just have 1 query to your Elasticsearch server. You won't be working with your objects anymore though if that's a requirement.

    This will add another layer of complexity, but can make searching/filtering/sorting easier in the future.