It was great to participate in the
Liferay North American Symposium this year. With hundreds of Liferay users (customers, partners, community members...) and dozens of presentations, it was not only a huge success but also a great opportunity to share user experiences and get your feedback. North American Symposium is over, but Liferay World Tour 2014 is not! There are still
many important events in our calendar so you still have the chance to learn about Liferay latest features firsthand.
Julio Camarero and I will be talking about Extensible Liferay Applications in the
Spanish Symposium next week and in the
Developer Conference in early November. This is probably one of the most relevant features in Liferay 6.2 because it's meant to completely change how Liferay applications are developed. Let's find out how with a simple example:
A Shipping Cost Calculator
Suppose you have an online shop and you need an application to calculate the final cost of purchasing an item, including its shipping to destination and considering not only the distance but also the currency, the local taxes and any other particularities. Thus, the final cost would be:
Final cost = [no. of items x item price] + [shipping cost to selected destination]
As a developer you could implement a very complex application that contains all possible shipping destinations. Every time you wanted to add or modify a shipping destination, you’d have to release a new version of your application. And likely your application would be more and more complex with every new release.
Alternatively, you could implement just the core functions of your calculator and define the shipping destinations as extensions to your application. This way, if you needed to add or modify a shipping destination those changes would not affect to the core functions, but only to an specific extension. With this approach, the release frequency of your core application as well as its complexity would decrease. Instead, new features would be added through small extensions with their own release frequency.
Modular and Extensible Applications: the OSGi Way
Probably at this point you’ve already realized the benefits of the second approach:
- Simpler maintenance of the core application by reducing its complexity
- Better performance (only required extensions would be installed)
- Support for third party extensions
- New market opportunities (e.g. purchasing shipping extensions)
Required Services for an Extensible Shipping Cost Calculator
OSGi services consist of:
- An interface, defining the service “contract”
- One or more implementations of the interface
To make our shipping cost calculator extensible, we need two types of OSGi services:
Shipping Extensions:
@Component(immediate = true, service = ShippingExtension.class)
public class ShippingExtensionUSA implements ShippingExtension {
ShippingExtension Registry
In order to have an up-to-date list with all the available shipping options, we need to track when these extensions (annotated with @Component) are deployed or undeployed. Through the @Referecene annotation the registerShippingExtension method of ShippingExtensionRegistryImpl is bound to the ShippingExtensionService, so it will be invoked every time an implementation of ShippingExtension is deployed. The unregisterShippingExtension method is called when an implementation is undeployed.
@Reference(
unbind = "unregisterShippingExtension",
cardinality = ReferenceCardinality.MULTIPLE,
policy = ReferencePolicy.DYNAMIC)
public void registerShippingExtension(ShippingExtension shippingExtension) {
_shippingExtensions.put(
shippingExtension.getShippingExtensionKey(), shippingExtension);
}
Accessing OSGi services from a non OSGi context: the ServiceTrackerUtil
We’re almost done. All we need to do is to list the shipping extensions registered by the ShippingExtensionRegistry in our GUI and process the resulting form according to the selected option. Since our GUI is still a Liferay portlet, which is not handled by the OSGi service container (yet), we cannot use the @Reference annotation to obtain the ShippingExtension service. Liferay provides a util class for this purpose: the ServiceTrackerUtil.
_shippingExtensionRegistry = ServiceTrackerUtil.getService(
ShippingExtensionRegistry.class, bundle.getBundleContext());
You can now test the app. First deploy all modules to your Liferay server, except for the shipping extensions. Then browse any site page and add the Shipping portlet. Notice that the calculator is functional, but it displays no shipping options. Now deploy the shipping extensions one by one, refreshing the page every time. You’ll now see a list with the available shipping extensions. Selecting a shipping extension will modify the form and the final result.
Going even deeper in modularization
If you have worked with the sample code, you may have noticed that the core application is not contained in a single project, but in three:
- shipping-api: Contains only the interfaces of the OSGi services that make up the app
- shipping-impl: Contains the implementation of the core OSGi services of the app
- shipping-web: Contains the user interface of the app
With this approach, the core of application can be easily modified by changing the implementation or the web interface, without changing the public API.
Audience Targeting is the first official Liferay application that is built following this
OSGi way, but this is actually how all Liferay apps will be in the next version of Liferay Portal so
WELCOME TO THE FUTURE!!