-
Notifications
You must be signed in to change notification settings - Fork 45
devon4j layers
As we already mentioned in the introduction to devon4j the components of our Java backend apps will be divided in three layers: service, logic and dataaccess.
-
Service Layer: will contain the REST services to exchange information with the client applications.
-
Logic Layer: the layer in charge of hosting the logic of the application (validations, authorization control, business logic, etc.).
-
Data Access Layer: the layer to communicate with the data base.
Following the devon4j recommendations for Dependency Injection in MyThaiStar’s layers we will find:
-
Separation of API and implementation: Inside each layer we will separate the elements in different packages: api and impl. The api will store the interface with the methods definition and inside the impl we will store the class that implements the interface.
-
Usage of JSR330: The Java standard set of annotations for dependency injection (
@Named
,@Inject
,@PostConstruct
,@PreDestroy
, etc.) provides us with all the needed annotations to define our beans and inject them.
@Named
public class MyBeanImpl implements MyBean {
@Inject
private MyOtherBean myOtherBean;
@PostConstruct
public void init() {
// initialization if required (otherwise omit this method)
}
@PreDestroy
public void dispose() {
// shutdown bean, free resources if required (otherwise omit this method)
}
}
The communication between layers is solved using the described Dependency Injection pattern, based on Spring and the Java standards: java.inject (JSR330) combined with JSR250.
import javax.inject.Inject;
import javax.inject.Named;
import io.oasp.application.mtsj.bookingmanagement.logic.api.Bookingmanagement;
@Named("BookingmanagementRestService")
public class BookingmanagementRestServiceImpl implements BookingmanagementRestService {
@Inject
private Bookingmanagement bookingmanagement;
@Override
public BookingCto getBooking(long id) {
return this.bookingmanagement.findBooking(id);
}
...
}
import javax.inject.Inject;
import javax.inject.Named;
import io.oasp.application.mtsj.bookingmanagement.dataaccess.api.dao.BookingDao;
@Named
public class BookingmanagementImpl extends AbstractComponentFacade implements Bookingmanagement {
@Inject
private BookingDao bookingDao;
@Override
public boolean deleteBooking(Long bookingId) {
BookingEntity booking = this.bookingDao.find(bookingId);
this.bookingDao.delete(booking);
return true;
}
...
}
As we mentioned at the beginning, the Service layer is where the services of our application (REST or SOAP) will be located.
In devon4j applications the default implementation for web services is based on Apache CXF, a services framework for Java apps that supports web service standards like SOAP (implementing JAX-WS) and REST services (JAX-RS).
In this tutorial we are going to focus only in the REST implementation of the services.
The services definition is done by the service interface located in the service.api.rest
package. In the boooking component of My Thai Star application we can see a service definition statement like the following
@Path("/bookingmanagement/v1")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public interface BookingmanagementRestService {
@GET
@Path("/booking/{id}/")
public BookingCto getBooking(@PathParam("id") long id);
...
}
JAX-RS annotations:
-
@Path: defines the common path for all the resources of the service.
-
@Consumes and @Produces: declares the type of data that the service expects to receive from the client and the type of data that will return to the client as response.
-
@GET: annotation for HTTP get method.
-
@Path: the path definition for the getBooking resource.
-
@PathParam: annotation to configure the id received in the url as a parameter.
The service implementation is a class located in the service.impl.rest
package that implements the previous defined interface.
@Named("BookingmanagementRestService")
public class BookingmanagementRestServiceImpl implements BookingmanagementRestService {
@Inject
private Bookingmanagement bookingmanagement;
@Override
public BookingCto getBooking(long id) {
return this.bookingmanagement.findBooking(id);
}
...
}
As you can see this layer simply delegates in the logic layer to resolve the app requirements regarding business logic.
In this layer we will store all the custom implementations to resolve the requirements of our applications. Including:
-
business logic.
-
Delegation of the transaction management to Spring framework.
-
object mappings.
-
validations.
-
authorizations.
Within the logic layer we must avoid including code related to services or data access, we must delegate those tasks in the suitable layer.
As in the service layer, the logic implementation will be defined by an interface located in a logic.api
package.
public interface Bookingmanagement {
BookingCto findBooking(Long id);
...
}
In a logic.impl
package a Impl class will implement the interface of the previous section.
@Named
@Transactional
public class BookingmanagementImpl extends AbstractComponentFacade implements Bookingmanagement {
/**
* Logger instance.
*/
private static final Logger LOG = LoggerFactory.getLogger(BookingmanagementImpl.class);
/**
* @see #getBookingDao()
*/
@Inject
private BookingDao bookingDao;
/**
* The constructor.
*/
public BookingmanagementImpl() {
super();
}
@Override
public BookingCto findBooking(Long id) {
LOG.debug("Get Booking with id {} from database.", id);
BookingEntity entity = getBookingDao().findOne(id);
BookingCto cto = new BookingCto();
cto.setBooking(getBeanMapper().map(entity, BookingEto.class));
cto.setOrder(getBeanMapper().map(entity.getOrder(), OrderEto.class));
cto.setInvitedGuests(getBeanMapper().mapList(entity.getInvitedGuests(), InvitedGuestEto.class));
cto.setOrders(getBeanMapper().mapList(entity.getOrders(), OrderEto.class));
return cto;
}
public BookingDao getBookingDao() {
return this.bookingDao;
}
...
}
In the above My Thai Star logic layer example we can see:
-
business logic and/or object mappings.
-
Delegation of the transaction management through the Spring’s
@Transactional
annotation.
In the code examples of the logic layer section you may have seen a BookingCto object. This is one of the Transfer Objects defined in devon4j to be used as transfer data element between layers.
Main benefits of using TO’s:
-
Avoid inconsistent data (when entities are sent across the app changes tend to take place in multiple places).
-
Define how much data to transfer (relations lead to transferring too much data).
-
Hide internal details.
In devon4j we can find two different _Transfer Objects:
-
Same data-properties as entity.
-
No relations to other entities.
-
Simple and solid mapping.
The third, and last, layer of the devon4j architecture is the one responsible for store all the code related to connection and access to data base.
For mapping java objects to the data base devon4j use the Java Persistence API(JPA). And as JPA implementation devon4j use hibernate.
Apart from the Entities of the component, in the dataaccess layer we are going to find the same elements that we saw in the other layers: definition (an interface) and implementation (a class that implements that interface).
However, in this layer the implementation is slightly different, the [Target]DaoImpl
extends general.dataaccess.base.dao.ApplicationDaoImpl
that provides us (through io.oasp.module.jpa
) with the basic implementation dataaccess methods: save(Entity)
, findOne(id)
, findAll(ids)
, delete(id)
, etc.
Because of that, in the [Target]DaoImpl
implementation of the layer we only need to add the custom methods that are not implemented yet. Following the My Thai Star component example (bookingmanagement) we will find only the paginated findBookings implementation.
public interface BookingDao extends ApplicationDao<BookingEntity> {
PaginatedListTo<BookingEntity> findBookings(BookingSearchCriteriaTo criteria);
}
@Named
public class BookingDaoImpl extends ApplicationDaoImpl<BookingEntity> implements BookingDao {
@Override
public PaginatedListTo<BookingEntity> findBookings(BookingSearchCriteriaTo criteria) {
BookingEntity booking = Alias.alias(BookingEntity.class);
EntityPathBase<BookingEntity> alias = Alias.$(booking);
JPAQuery query = new JPAQuery(getEntityManager()).from(alias);
...
}
}
The implementation of the findBookings uses queryDSL to manage the dynamic queries.
All the above sections describe the main elements of the layers of the devon4j components. If you have completed the exercise of the previous chapter you may have noticed that all those components are already created for us by Cobigen.
Take a look to our application structure
visitor component core (without relations)
-
1. Definition for dataaccess layer repository.
-
2. The entity that we created to be used by Cobigen to generate the component structure.
-
3. Definition of abstract usecase in the logic layer.
-
4. Implementation of the usecasefind layer in the logic layer.
-
5. Implementation of the usecasemanage layer in the logic layer.
-
6. Implementation of the logic layer.
-
7. Implementation of the rest service.
visitor component api (without relations)
-
1. definition for entity in the api layer.
-
2. Entity Transfer Object located in the api layer.
-
3. Search Criteria Transfer Object located in the api layer.
-
4. Definition of usecasefind in the logic layer.
-
5. Definition of usecasemanage in the logic layer.
-
6. Definition of the logic layer.
-
7. Definition of the rest service of the component.
For the queue component you will find a similar structure.
The component access code will have a similar structure adding some differences since it has some relations with visitor and queue.
access code component core (with relations)
Theres only a single difference in the core, if you look closely, cobigen didnt generate the usecasemanage implementation. This is due to complexity of the entities with relations. In this case cobigen will leave us to create the save and delete methods so we can properly adress them.
access code component api (with relations)
Theres two differences here:
-
As you can see cobigen generated a cto for our entity with relations
-
Like we explained in the core, the usecasemanage definition is missing.
So, as you can see, our components have all the layers defined and implemented following the devon4j principles.
Using Cobigen we have created a complete and functional devon4j application without the necessity of any manual implementation except for more complex entities which will be explained to the next chapter.
Let’s see the application running and let’s try to use the REST service to save a new visitor.
As we already mentioned, for this tutorial we are using Postman app for desktop, but you can use any other similar tool to test your API.
First, open your Jump the Queue project in Eclipse and run the app (right click over the _SpringBootApp.java class > Run as > Java application)
If you remember we added some mock data to have some visitors info available, let’s try to retrieve a visitor’s information using our visitormanagement service.
Call the service (GET) http://localhost:8081/jumpthequeue/services/rest/visitormanagement/v1/visitor/1/
to obtain the data of the visitor with id 1.
Instead of receiving the visitor’s data we get a response with the login form. This is because the devon4j applications, by default, implements the Spring Security so we would need to log in to access to the services.
To ease the example we are going to "open" the application to avoid the security filter and we are going to enable the CORS filter to allow requests from clients (Angular).
In the file general/service/impl/config/BaseWebSecurityConfig.java
:
-
edit the
configure(HttpSecurity http)
method, comment the http request filter and authorize every request to allow access to the app to any request:
@Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().anyRequest().permitAll().and().csrf().disable();
if (this.corsEnabled) {
http.addFilterBefore(getCorsFilter(), CsrfFilter.class);
}
}
Finally in the file /jtqj-core/src/main/resources/application.properties
set security.cors.enabled
to true
security.cors.enabled=true
Now run again the app and try again the same call. We should obtain the data of the visitor
Cobigen has created for us a complete services related to our entities so we can access to a paginated list of the visitors without any extra implementation.
We are going to use the following service defined in visitormanagement/service/api/rest/VisitormanagementRestService.java
@Path("/visitor/search")
@POST
public Page<VisitorEto> findVisitors(VisitorSearchCriteriaTo searchCriteriaTo);
The service definition states that we will need to provide a Search Criteria Transfer Object. This object will work as a filter for the search as you can see in visitormanagement/dataaccess/api/repo/VisitorRepository.java
in findByCriteria method.
If the Search Criteria is empty we will retrieve all the visitors, in other case the result will be filtered.
Call the service POST: http://localhost:8081/jumpthequeue/services/rest/visitormanagement/v1/visitor/search
in the body we need to define the SearchCriteria object, that will have a pageable defined:
{
"pageable" : {
"pageNumber" : "0",
"pageSize": "10"
}
}
Make sure, the header Content-Type: application/json
is passed as well indicating the server to interprete the body in JSON format and check "raw". Otherwise, you may face an 415 unsuported type error.
ℹ️
|
You can see the definition of the SearchCriteriaTo in |
The result will be in the Headers section something like
If we want to filter the results we can define a criteria object in the body. Instead of previous empty criteria, if we provide an object like
{
"username": "test1@mail.com",
"pageable" : {
"pageNumber" : "0",
"pageSize": "10"
}
}
we will filter the results to find only visitors with username test1@mail.com. If now we repeat the request the result will be
We could customize the filter editing the visitormanagement/logic/impl/usecase/UcFindVisitorImpl.java
class.
To fit the requirements of the related user story we need to register a visitor and return an access code.
By default Cobigen has generated for us the Read operation in the UcFindEntityImpl and rest of the CRUD in UcManageEntityImpl. So we already are able to read, update, delete and create a visitor in our database without extra implementation.
To delegate in Spring to manage the transactions we only need to add the @Transactional
annotation to our usecase implementations. Since devonfw 2.2.0 Cobigen adds this annotation automatically, so we don’t need to do it manually. Check your logic implementation classes and add the annotation in case it is not present.
@Named
@Validated
@Transactional
public class UcManageVisitorImpl extends AbstractVisitorUc implements UcManageVisitor {
...
}
To save a visitor we only need to use the REST resource /services/rest/visitormanagement/v1/visitor
and provide in the body the visitor definition for the VisitorEto.
ℹ️
|
You can see the definition for VisitorEto in |
So, call (POST) http://localhost:8081/jumpthequeue/services/rest/visitormanagement/v1/visitor
providing in the body a Visitor object like
{
"username": "mary@mail.com",
"name": "Mary",
"phoneNumber": "1234567",
"password": "12345",
"acceptedCommercial": "true",
"acceptedTerms": "true",
"userType": "false"
}
We will get a result like the following
In the body of the response we can see the default content for a successful service response: the data of the new visitor. This is the default implementation when saving a new entity with devon4j applications. However, the Jump the Queue design defines that the response must provide the access code created for the user, so we will need to change the logic of our application to fit this requirement.
In the next chapter we will see how we can customize the code generated by Cobigen to adapt it to our necessities.
Next chapter: Customizing an devon4j project
This documentation is licensed under the Creative Commons License (Attribution-NoDerivatives 4.0 International).