This chapter focuses on the Service Builder. The Service Builder is the easiest way to create a service and a DAO layer for portlets with the help of a master file. A lot of boilerplate code is generated when services are built using this file. This file helps generate the mappings. In this chapter, you learn everything about this process and the different ways to fetch data from the database.
Introduction to the Service Builder
The Service Builder is one tool that makes Liferay stand out in connecting services to custom portlets in the database. It’s easy, accurate, and effective. The Service Builder can generate all necessary objects, classes, and methods for database connectivity with a custom module. In short, you can say that it generates a complete service and a DAO layer. Not only this, but it also generates SQL scripts so that when the portlet is deployed, it will generate the necessary tables in the database. The Service Builder is dependent on a file called service.xml. All magic starts in this file. This file contains all the information needed for service generation. Liferay DXP's Service Builder is slightly different than the older version. Now, it does not generate a service.jar file. Instead, it creates two new OSGi modules. Don’t worry, as you’ll learn about them in an upcoming section of the chapter.
As Liferay’s official documentation explains, “Service Builder is a model-driven code generation tool built by Liferay that allows developers to define custom object models called entities.” Service Builder uses object-relational mapping (ORM) to generate a service layer. It provides a clear distinction between the object model and code for the database. These saves developers time in implementing those. It also offers built-in caching support (using Ehcache) to accelerate service execution. Service Builder lets developers use custom SQL and dynamic queries for any complex query depending on business logic. How ORM maps with relations is shown in Figure 5-1.
This section has explained the basics of Service Builder; in the next section, you explore how to build services using Service Builder.
Generating Services
The first thing to understand is that Service Builder is a service.xml file. Using only this file, ORM is defined. You need to specify the entity names and fields, and then, when you build the service, the DAO layer is built.
- 1.
Open Liferay Developer Studio to the apress_ws workspace, which you used to create the Liferay portlets in Chapters 3 and 4. You use the same workspace so that all the Liferay-created code is in one place. Right-click the Modules folder and then choose New ➤ Liferay Module Project. (See Figure 5-2.)
- 2.
Select Gradle for the build type, as you are building Gradle-based modules. (See Figure 5-2.)
- 3.
Name the Project Template Name service-builder. This template will help you create a Liferay Service Builder. Click Next.
- 4.
Once you click Finish, Developer Studio automatically creates your apress service engine, which will contain all the service-related classes and files. The service.xml file is the backbone of the Service Builder. (See Figure 5-3.)
Sample service.xml
- 1.
Define global information for the service. These settings are applied to all the services of the entities generated by this service.xml file. For example, package path, author, and so on, as shown in Figure 5-4.
- 2.These fields will appear as follows in service.xml:<service-builder dependency-injector="ds" package-path="com.handsonliferay.apress_service_builder"><Author>Apoorva_Inthiyaz</author><namespace>apress</namespace>
- 3.
Define the service entities. Service entities are models in code and tables in the database. This is the first place where ORM starts taking place. (See Figure 5-5.)
- 4.
Define the attributes. Attributes are the columns in the table and member variables for the models. This is the next step of ORM, as shown in Figure 5-6.
- 5.
Define relationships between entities.
- 6.
Define a default order for the data to be retrieved from the database, which can be ascending or descending. These are in the form of the entities.
As an example, service.xml is shown here:<order by="asc"><order-column name="chapterId" /> </order> - 7.
Define the finder methods to retrieve data from the database based on specified parameters. This could be one object or an array of objects.
As an example, service.xml is shown here:<finder name="IsCoding" return-type="Collection"><finder-column name="isCoding" /></finder>
Let’s review all this with a complete example.
This section has explained how to build services using the Service Builder; in the next section, you learn about the code generated by the Service Builder.
Deep Diving Into the Code Generated by the Service Builder
Persistence layer: This layer is used to save and retrieve entities to and from the database.
Model layer: This layer is used to define objects to represent your project’s entities.
Services layer: This is a blank layer ready for you to create your API and business logic.
Customization via Implementation Classes
Entity implementation: This is responsible for customizing the entity; all these classes will have the extension *Impl.Java.
ApressBookLocalServiceImpl Class
ApressBookLocalServiceUtil Class
Remote Service Implementation
You already know what a local service is. A remote service, on the other hand, is a service that can be accessed from resources running outside of the application context. Liferay DXP allows you to expose web services as JSON and SOAP web services. Even various Liferay services are available in the form of web services.
To generate remote services for your custom entities, you must run the Service Builder with the remote-service attribute set to true. After setting this, you need to build the service again, which will generate all the classes, interfaces, and files required to support SOAP and JSON web services. Once the service is built, it provides *ServiceImpl, as shown in Listing 5-4, where you need to write the business logic for implementing remote services. The best practice when implementing a remote service is to add a proper permission check because remotes services are open for access from other applications.
ApressBookLocalServiceUtil Class
CRUD Operations
Create: The Create operation is an INSERT operation in SQL. A new value is inserted into the database.
Read: The Read operation is a SELECT operation in SQL. Values are fetched from the database. You’ll see this in detail in the “Finder” section of this chapter.
Update: The Update operation is an UPDATE operation in SQL. Inserted values can be modified.
Delete: The Delete operation is DELETE in SQL. You can delete the existing values from the database.
Adding Service Builder Dependencies to the build.gradle File
compileOnly project(":modules:apress_service_builder:apress_service_builder-api") is used in the build.gradle file, which enables apress_service_builder-api classes to be available to the apressMVC portlet.
compileOnly project(":modules:apress_service_builder:apress_service_builder-service") is used in the build.gradle file, which enables apress_service_builder-service classes to be available.
Code Snippet to Create an Entry in a Custom Table
Code Snippet to Update an Entry in a Custom Table
Code Snippet to Delete an Entry in a Custom Table
Code Snippet to Read an Entry from a Custom Table
Finders
Liferay Service Builder provides a straightforward approach for fetching data from database table columns. These methods are called finder methods. They are easy to implement but have a drawback: they are fit for simple fetch operations, not for complex ones. To generate a finder method, you must add a finder tag to the service.xml file and configure it accordingly.
Data retrieved by the finder methods is in the form of model objects that fulfill the specified criteria. The Service Builder generates several methods based on each finder created for an entity. It creates methods to fetch, find, remove, and count entity instances based on the finder’s parameters.
- 1.
Write a service.xml file (see Listing 5-10).
Writing a Finder in Service.xml
- 2.
Write the business logic for the custom finder in the Entity Local Service Impl class.
- 3.
After these changes, you need to add buildService to the Service Builder module. (See Listing 5-11.)
Writing Finder Business Logic in the ApressBookLocalServiceImpl Class
- 4.
Invoke the custom implemented finder in the custom module (see Listing 5-12).
Invoking the Custom Implemented Finder
Dynamic Query
Liferay allows custom SQL queries to retrieve data from the database. However, in real-world applications, you’ll need to build queries dynamically. (If you do not want to build the query dynamically, you can use Custom SQL, which you’ll see in the next section.) Returning to dynamic queries, Liferay provides the DynamicQuery API. The DynamicQuery API is a wrapper of the Hibernates Criteria API.
When creating a dynamic query, a SQL query is generated by the code using the DynamicQuery API, where you write Java code, not SQL. In the DynamicQuery API, the query is written as Java code, where variables and objects are used instead of tables and columns. These queries are simple to write and implement in comparison to SQL queries.
Dynamic Query Code Snippet for a Custom Table
Custom SQL
The Service Builder generates finder methods that fetch values for the tables. You learned about finders and dynamic queries in the previous sections. Real-world applications are sometimes too complex to be covered by simple finders and dynamic queries. In those cases, custom SQL is implemented.
Custom SQL gives you the liberty to directly execute SQL statements for implementation, but great power comes with great responsibility. Custom SQL comes with the drawback—if the application’s database changes, you may have to modify the query based on the database engine’s query syntax. For instance, if you were using MySQL as a portal database (so all the custom SQL is written in MySQL) and then later you move the database to Oracle, the Custom SQL queries need to be modified according to the Oracle syntax. More complex cases such as joins are the major cases where these are implemented. You learn how to implement Custom SQL in this tutorial.
Liferay custom SQL is supported by the Service Builder method. It helps execute custom complex queries against the database by invoking custom SQL from a finder method in your persistence layer. The Service Builder enables you to generate these interfaces in your finder method.
- 1.
Specify your Custom SQL.
You need to specify it in a particular file so Liferay can access it. The CustomSQLUtil class (from the com.liferay.portal.dao.orm.custom.sql module) retrieves the SQL from a file called default.xml in your service module’s src/main/resources/META-INF/custom-sql/ folder. You must create the custom-sql folder and a default.xml file in that custom-sql folder. The default.xml file must adhere to the format shown in Listing 5-14.
The default.xml File for the Custom SQL
- 2.
Implement your custom finder method.
Service Builder generates the interface for the finder in your API module. Still, you have to create the implementation to implement the finder method in the persistence layer to invoke your custom SQL query. First, you need to create a *FinderImpl class in the service persistence package. In the Apressbook application, you could create an EntryFinderImpl class in the com.apress.handsonliferay.service.persistence.impl package. Your class should extend BasePersistenceImpl<Entry>.
Run the Service Builder to generate the *Finder interface based on the *FinderImpl class. Modify your *FinderImpl class to make it a component (annotated with @Component) that implements the *Finder interface you just generated. (See Listing 5-15.)
EntryFinderImpl for the Custom SQL
- 3.
Access your finder method from the service.
Working with Remote Services
Now that you understand how to create a service and consume it in your controller class, there is another type that can be consumed from other applications as well. These services are referred to as remote services. They come in handy when you need your application to communicate with other applications of ecology. Liferay provides features to expose services as remote components that are straightforward and easy to maintain. Liferay offers two approaches to achieve this, discussed in the following sections.
Headless REST APIs
As their name suggests, these services are not consumed in the application exposing the services. In a way, your application works as a microservice host, a new-age way of working with APIs. These services are RESTful web services, independent of Liferay DXP’s front-end. This is why they are called headless. These APIs fulfill the OpenAPI specification.
Liferay DXP’s headless REST APIs leverage OpenAPI (known initially as Swagger); you don’t need a service catalog. You only need to know the OpenAPI profile to discover the rest of the APIs to begin consuming the web service.
Liferay DXP’s headless APIs are available in SwaggerHub at https://app.swaggerhub.com/organizations/liferayinc.
Each API has its own URL in SwaggerHub.
For example, you can access the delivery API definition at https://app.swaggerhub.com/apis/liferayinc/headless-delivery/v1.0.
Each OpenAPI profile is also deployed dynamically in your portal instance under this schema:
http://[host]:[port]/o/[insert-headless-api]/[version]/openapi.yaml
For example, if you’re running Liferay DXP locally on port 8080, the home URL for discovering the headless delivery API is:
http://localhost:8080/o/headless-delivery/v1.0/openapi.yaml
You must be logged in to access this URL or use basic authentication and a browser. You can also use other tools like Postman, an advanced REST client, or even the curl command from your system console.
Let’s look at this process in more detail with an example.
- 1.
Create a project using Blade, as follows:
- 2.
Edit this project’s build.gradle file to get the REST builder Gradle plugin. The REST builder Gradle plugin lets you generate a REST layer that’s defined in the REST builder’s rest-config.yaml and rest-openapi.yaml files. To use this plugin, include it in your build script, as shown in Listing 5-16.
The Build Script
- 3.
Quickly check the tasks by using ./gradlew tasks from the command prompt to the books project. The output is shown in Listing 5-17.
Execution of the Gradle Tasks Command
- 4.
Create a handful of modules.
- 5.
Create the YAML files that will define the service endpoints. To headless-books-impl, you need to add the rest-config.yaml file, as shown in Listing 5-18. The rest-openapi.yaml file must also be created in your headless-books-impl module.
The rest-config.yaml File
The Meta Section of the File
Reusable Components
In YAML file indents signify depth, so a line at a higher indent is a child, and a line at the same depth is a sibling. The creator is a reference to another object in the file (that's $ref). When you do have a $ref in the same file, it means you need to include the reference.
Plain Web/REST Services
This is the legacy way to build and consume web services in Liferay DXP, but it is still supported. This lets you use JAX-RS, JAX-WS, or the Service Builder to implement plain REST or SOAP web services. You learned how to implement these services in a previous section of this chapter, where implementation of web service is explained.
ApressBookServiceImpl with a Custom Method
Rebuild the services and redeploy your app’s modules. You can now invoke this service method via JSON.
ApressBookServiceImpl with the addBook Method Ways
This concludes how a service can be exposed from Liferay DXP.
Summary
In this chapter, you learned about the easiest way to create a service and DAO layer for a portlet, which is using the Service Builder. The Service Builder uses the service.xml file to generate the ORM and its methods. You can do CRUD operations using these generated methods. To perform CRUD operations, local and remote services can be used. Local and remote services can be invoked, depending on the context in which the service is being invoked. To fetch data from tables, you can implement finders, dynamic queries, or Custom SQL, depending on the complexity of the query.
In the next chapter, you will go through different ways of customizing Liferay DXP.