-
Notifications
You must be signed in to change notification settings - Fork 45
devon4j adding custom functionality
In the previous chapter we have seen that using Cobigen we can generate in a few clicks all the structure and functionality of an devon4j component.
In this chapter we are going to show how to add custom functionalities in our projects that are out of the scope of the code that Cobigen is able to cover.
The Jump the Queue design defines a User Story in which a visitor can register into an event and obtain an access code to avoid a queue.
In our standard implementation of the Jump the queue app we have used Cobigen to generate the components, so we have a default implementation of the services. Since AccessCode component is more complex and needs the use of CTOs we need to create our own usecasemanage and the methods save and delete.
It is also needed to add some methods to the Queue component since when saving/deleting an AccessCode
the Queue
customers needs to increase/decrease.
In our case, two new methods are going to be needed, decreaseQueueCustomer
and increaseQueueCustomer
. In order to add those methods to the queuemanagement we need to do three steps:
-
Modify the corresponding
usecase
interface adding the methods. -
Implement the methods in the
usecaseimpl
. -
Modify the management implementation
managementimpl
.
Inside jtqj-api/queuemanagement/log/api/usecase/UcManageQueue
the declaration of the two methods are going to be added:
....
public interface UcManageQueue {
....
/**
* Decrease number of customers of the queue and update the queue.
*
* @param queueId id of the queue to decrease customer.
*/
void decreaseQueueCustomer(long queueId);
/**
* Increase number of customers of the queue and update the queue.
*
* @param queueId id of the queue to increase customer.
*/
void increaseQueueCustomer(long queueId);
....
}
In the jtqj-core/src/main/java/queuemanagement/logic/impl/usecase/UcManageQueueImpl
the implementation of the methods that were just added in the interface are going to be added.
public class UcManageQueueImpl extends AbstractQueueUc implements UcManageQueue {
....
@Override
public void decreaseQueueCustomer(long queueId) {
// Using the repository find method and queueId paremeter the queue is found
QueueEntity queueEntity = getQueueRepository().find(queueId);
// the customers gets reduced by one
queueEntity.setCustomers(queueEntity.getCustomers() - 1);
// Based on hibernate, the command save(Entity) is not strictly required, but it improves readability.
// the queueEntity gets saved
getQueueRepository().save(queueEntity);
}
@Override
public void increaseQueueCustomer(long queueId) {
// Using the repository find method and queueId paremeter the queue is found
QueueEntity queueEntity = getQueueRepository().find(queueId);
// the customers gets increased by one
queueEntity.setCustomers(queueEntity.getCustomers() + 1);
// Based on hibernate, the command save(Entity) is not strictly required, but it improves readability.
// the queueEntity gets saved
getQueueRepository().save(queueEntity);
}
}
Since the Queuemanagement
extends the usecase UcManageQueue
the methods from the previous step need to be added in the QueuemanagementImpl
.
....
public class QueuemanagementImpl extends AbstractComponentFacade implements Queuemanagement {
....
@Override
public void decreaseQueueCustomer(long queueId) {
this.ucManageQueue.decreaseQueueCustomer(queueId);
}
@Override
public void increaseQueueCustomer(long queueId) {
this.ucManageQueue.increaseQueueCustomer(queueId);
}
....
}
These methods are simply going to call the ucManageQueue
methods that were just added.
Before creating the usecasemanage a method needs to be added to the usecasefind that will recover our AccessCodeEto
. In jtqj-api
, inside the package accesscodemanagement/logic/api/usecase/
the file UcFindAccessCode
is going to be modified adding the new method to the interface:
....
import com.devonfw.application.jtqj.accesscodemanagement.logic.api.to.AccessCodeEto;
....
public interface UcFindAccessCode {
....
/**
* Returns a paginated list of AccessCodeEto matching the search
* criteria.
*
* @param criteria the {@link AccessCodeSearchCriteriaTo}.
* @return the {@link List} of matching {@link AccessCodeEto}s.
*/
Page<AccessCodeEto> findAccessCodeEtos(AccessCodeSearchCriteriaTo criteria);
....
}
Once that is finished we will see that an error is going to appear in UcFindAccessCodeImpl
and AccesscodemanagementImpl
, this second error will be solved in later steps. To solve the first error, in the jtqj-core
the accesscodemanagement/logic/impl/usecase/UcFindAccessCodeImpl
needs to implements the last method:
public class UcFindAccessCodeImpl extends AbstractAccessCodeUc implements UcFindAccessCode {
....
@Override
public Page<AccessCodeEto> findAccessCodeEtos(AccessCodeSearchCriteriaTo criteria) {
Page<AccessCodeEntity> accessCodes = getAccessCodeRepository().findByCriteria(criteria);
return mapPaginatedEntityList(accessCodes, AccessCodeEto.class);
}
....
}
This method uses a AcessCodeSearchCriteriaTo
to find a page of entities AccessCodeEntity
with the repository. After that, it maps the list changing from AccessCodeEntity
to AccessCodeEto
.
In the api(jtqj-api
) inside the package accesscodemanagement/logic/api/usecase/
we are going to create a new interface called UcManageAccessCode
, where we will define the save and delete methods.
import com.devonfw.application.jtqj.accesscodemanagement.logic.api.to.AccessCodeEto;
public interface UcManageAccessCode {
/**
* Deletes a accessCode from the database by its id 'accessCodeId'.
* Decreases the count of customers of the queue assigned to the access
* code by one
*
* @param queueId Id of the queue to delete
*/
void deleteAccessCode(long accessCodeId);
/**
* Saves a queue and store it in the database.
* Increases the count of customers of the queue assigned to the access
* code by one
*
* @param queue the {@link AccessCodeEto} to create.
* @return the new {@link AccessCodeEto} that has been saved with ID and version.
*/
AccessCodeEto saveAccessCode(AccessCodeEto accessCodeEto);
}
Then in the core(jtqj-core
) inside the package accesscodemanagement/logic/impl/usecase
we are going to create a class called UcManageAccessCodeImpl
implementing the definition we just made and extending AbstractAccessCodeUc
, this will allow us to have access to the repository. Also, Here is the part where we will use the methods that were created in the Queue
component.
import java.sql.Timestamp;
import java.time.Instant;
import java.util.List;
import java.util.Objects;
import javax.inject.Inject;
import javax.inject.Named;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import com.devonfw.application.jtqj.accesscodemanagement.dataaccess.api.AccessCodeEntity;
import com.devonfw.application.jtqj.accesscodemanagement.logic.api.to.AccessCodeCto;
import com.devonfw.application.jtqj.accesscodemanagement.logic.api.to.AccessCodeEto;
import com.devonfw.application.jtqj.accesscodemanagement.logic.api.to.AccessCodeSearchCriteriaTo;
import com.devonfw.application.jtqj.accesscodemanagement.logic.api.usecase.UcFindAccessCode;
import com.devonfw.application.jtqj.accesscodemanagement.logic.api.usecase.UcManageAccessCode;
import com.devonfw.application.jtqj.accesscodemanagement.logic.base.usecase.AbstractAccessCodeUc;
import com.devonfw.application.jtqj.queuemanagement.dataaccess.api.QueueEntity;
import com.devonfw.application.jtqj.queuemanagement.logic.api.Queuemanagement;
import com.devonfw.application.jtqj.queuemanagement.logic.api.to.QueueEto;
import com.devonfw.application.jtqj.queuemanagement.logic.impl.usecase.UcManageQueueImpl;
@Named
@Validated
@Transactional
public class UcManageAccessCodeImpl extends AbstractAccessCodeUc implements UcManageAccessCode {
@Inject
private Queuemanagement queuemanagement;
@Inject
private Accesscodemanagement accesscodemanagement;
/** Logger instance. */
private static final Logger LOG = LoggerFactory.getLogger(UcManageQueueImpl.class);
@Override
public void deleteAccessCode(long accessCodeId) {
// Using the AccessCodeRepository we get the queueId
long queueId = getAccessCodeRepository().find(accessCodeId).getQueueId();
/**
* Using the method getQueuemanagement() gives access to the methods that were created earlier
* in the usecasemanage(inside the queue component). This is done so each component takes care of its own modifications.
*/
this.queuemanagement.decreaseQueueCustomer(queueId);
LOG.debug("The queue with id '{}' has decreased its customers.", queueId);
// then we delete the accesscode
getAccessCodeRepository().deleteById(accessCodeId);
LOG.debug("The accesscode with id '{}' has been deleted.", accessCodeId);
}
@Override
public AccessCodeEto saveAccessCode(AccessCodeEto accessCodeEto) {
// We make sure the object is not null
Objects.requireNonNull(accessCodeEto, "UcManageAccessImpl accessCode null");
AccessCodeEntity accessCodeEntity = getBeanMapper().map(accessCodeEto, AccessCodeEntity.class);
long queueEntityId = accessCodeEntity.getQueueId();
AccessCodeSearchCriteriaTo accessCodeSearchCriteriaTo = new AccessCodeSearchCriteriaTo();
accessCodeSearchCriteriaTo.setQueueId(queueEntityId);
Pageable pageable = PageRequest.of(0, 1000);
accessCodeSearchCriteriaTo.setPageable(pageable);
/**
* Calling the parent with the method getAccesscodemanagement() we use the method findAccessCodeEtos()
* that will call then implemention of the method inside (UcFindAccessCodeImpl) through the interface.
* This allows us to use the {@link UcFindAccessCodeImpl}
*/
List<AccessCodeEto> accessCodeEtosInQueue = getAccesscodemanagement().findAccessCodeEtos(accessCodeSearchCriteriaTo)
.getContent();
// if theres no etos we set the ticket to the first code
// else we get the digit of the last ticket in the list and generate a new code
// for the ticket
if (accessCodeEtosInQueue.isEmpty()) {
accessCodeEntity.setTicketNumber("Q000");
} else {
AccessCodeEto lastAccessCode = accessCodeEtosInQueue.get(accessCodeEtosInQueue.size() - 1);
int lastTicketDigit = Integer.parseInt(lastAccessCode.getTicketNumber().substring(1));
accessCodeEntity.setTicketNumber(generateTicketCode(lastTicketDigit));
}
// we set the creation time, startTime and endTime
accessCodeEntity.setCreationTime(Timestamp.from(Instant.now()));
accessCodeEntity.setStartTime(null);
accessCodeEntity.setEndTime(null);
// save the AccessCode
AccessCodeEntity accessCodeEntitySaved = getAccessCodeRepository().save(accessCodeEntity);
LOG.debug("The accesscode with id '{}' has been saved.", accessCodeEntitySaved.getId());
/**
* Using the method getQueuemanagement() gives access to the methods that were created earlier
* in the usecasemanage(inside the queue component). This is done so each component takes care of its own modifications.
*/
getQueuemanagement().increaseQueueCustomer(accessCodeEntitySaved.getQueueId());
LOG.debug("The queue with id '{}' has increased its customers.", accessCodeEntitySaved.getQueueId());
return getBeanMapper().map(accessCodeEntitySaved, AccessCodeEto.class);
}
/**
* Generates a new ticked code using the ticket digit of the last codeaccess
* created
*
* @param lastTicketDigit the int of the last codeaccess created
* @return the String with the new ticket code (example: "Q005");
*/
public String generateTicketCode(int lastTicketDigit) {
int newTicketDigit = lastTicketDigit + 1;
String newTicketCode = "";
if (newTicketDigit == 1000) {
newTicketCode = "Q000";
} else {
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append(newTicketDigit);
while (stringBuilder.length() < 3) {
stringBuilder.insert(0, "0");
}
stringBuilder.insert(0, "Q");
newTicketCode = stringBuilder.toString();
}
return newTicketCode;
}
public Queuemanagement getQueuemanagement() {
return this.queuemanagement;
}
public Accesscodemanagement getAccesscodemanagement() {
return this.accesscodemanagement;
}
}
Taking a closer look into the code, we can see that in order to use the methods from the UcFindAccessCodeImpl
we need to use the parent(Accesscodemanagement
) instead of the class directly. Also, following the devon4j
structure each component needs to take care of its own. In this case, by using the method getQueuemanagement()
we get access to the Queuemanagement
injection that will allow the use of the methods we created earlier in the use cases in the queue component.
Inside jtqj-api
and in the class accesscodemanagement/logic/api/AccessCodemanagement
we are going to extend the UcManageAccessCode
that we just defined
import com.devonfw.application.jtqj.accesscodemanagement.logic.api.usecase.UcFindAccessCode;
import com.devonfw.application.jtqj.accesscodemanagement.logic.api.usecase.UcManageAccessCode;
/**
* Interface for Accesscodemanagement component.
*/
public interface Accesscodemanagement extends UcFindAccessCode,UcManageAccessCode {
}
After that, on the jtqj-core
in the class accesscodemanagement/logic/impl/AccesscodemanagementImpl
we will see that an error has appeared because the methods
from the extended interfaces are missing. We add the unimplemented methods and inject the usecasemanage
solving the error.
import javax.inject.Inject;
import javax.inject.Named;
import org.springframework.data.domain.Page;
import com.devonfw.application.jtqj.accesscodemanagement.logic.api.Accesscodemanagement;
import com.devonfw.application.jtqj.accesscodemanagement.logic.api.to.AccessCodeCto;
import com.devonfw.application.jtqj.accesscodemanagement.logic.api.to.AccessCodeEto;
import com.devonfw.application.jtqj.accesscodemanagement.logic.api.to.AccessCodeSearchCriteriaTo;
import com.devonfw.application.jtqj.accesscodemanagement.logic.api.usecase.UcFindAccessCode;
import com.devonfw.application.jtqj.accesscodemanagement.logic.api.usecase.UcManageAccessCode;
import com.devonfw.application.jtqj.general.logic.base.AbstractComponentFacade;
/**
* Implementation of component interface of accesscodemanagement
*/
@Named
public class AccesscodemanagementImpl extends AbstractComponentFacade implements Accesscodemanagement {
@Inject
private UcFindAccessCode ucFindAccessCode;
@Inject
private UcManageAccessCode ucManageAccessCode;
@Override
public AccessCodeCto findAccessCodeCto(long id) {
return this.ucFindAccessCode.findAccessCodeCto(id);
}
@Override
public Page<AccessCodeCto> findAccessCodeCtos(AccessCodeSearchCriteriaTo criteria) {
return this.ucFindAccessCode.findAccessCodeCtos(criteria);
}
@Override
public void deleteAccessCode(long accessCodeId) {
this.ucManageAccessCode.deleteAccessCode(accessCodeId);
}
@Override
public AccessCodeEto saveAccessCode(AccessCodeEto accessCodeEto) {
return this.ucManageAccessCode.saveAccessCode(accessCodeEto);
}
@Override
public Page<AccessCodeEto> findAccessCodeEtos(AccessCodeSearchCriteriaTo criteria) {
return this.ucFindAccessCode.findAccessCodeEtos(criteria);
}
}
To add the new service we need to add the definition to the accesscodemanagement/service/api/rest/AccesscodemanagementRestService.java
. We are going to create a new /acessCode
REST resource bound three methods, one called saveAccessCode, another one called findAccessCodeEtos and the other one called deleteAccessCode.
....
public interface AccesscodemanagementRestService {
....
/**
* Delegates to {@link Accesscodemanagement#findAccessCodeEtos}.
*
* @param searchCriteriaTo the pagination and search criteria to be used for
* finding accesscodes.
* @return the {@link Page list} of matching {@link AccessCodeEto}s.
*/
@Path("/accesscode/search")
@POST
public Page<AccessCodeEto> findAccessCodeEtos(AccessCodeSearchCriteriaTo searchCriteriaTo);
/**
* Delegates to {@link Accesscodemanagement#saveAccessCode}.
*
* @param queue the {@link AccessCodeEto} to be saved
* @return the recently created {@link AccessCodeEto}
*/
@POST
@Path("/accesscode/")
public AccessCodeEto saveAccessCode(AccessCodeEto accessCodeEto);
/**
* Delegates to {@link Accesscodemanagement#deleteAccessCode}.
*
* @param id ID of the {@link AccessCodeEto} to be deleted
*/
@DELETE
@Path("/accesscode/{id}/")
public void deleteAccessCode(@PathParam("id") long id);
}
Then we need to implement the new methods in accesscodemanagement/service/impl/rest/AccesscodemanagementRestServiceImpl.java
class.
....
public class AccesscodemanagementRestServiceImpl implements AccesscodemanagementRestService {
....
@Override
public AccessCodeEto saveAccessCode(AccessCodeEto accessCodeEto) {
return this.accesscodemanagement.saveAccessCode(accessCodeEto);
}
@Override
public void deleteAccessCode(long id) {
this.accesscodemanagement.deleteAccessCode(id);
}
@Override
public Page<AccessCodeEto> findAccessCodeEtos(AccessCodeSearchCriteriaTo searchCriteriaTo) {
return this.accesscodemanagement.findAccessCodeEtos(searchCriteriaTo);
}
....
}
Testing the changes Now run again the app with Eclipse and with postman call our new save service (POST) http://localhost:8081/jumpthequeue/services/rest/accesscodemanagement/v1/accesscode/ providing in the body a AccessCode object with the parameters needed:
{
"queueId":"1",
"visitorId":"1000000"
}
The result should be something similar to this:
In order to know if the new codeaccess has been succesfully created we can search all the ctos like we did in anterior steps, the new accesscode should be on the bottom:
To test the delete, you can send a delete to this url http://localhost:8081/jumpthequeue/services/rest/accesscodemanagement/v1/accesscode/{id} using the id found either on the save or on the search.
In this chapter we have seen how easy is extend a devon4j application, with few steps you can add new services to your backend app to fit the functional requirements of your projects or edit them to adapt the default implementation to your needs.
In the next chapter we will show how easy is to add validations for the data that we receive from the client.
Next chapter: Validations in devon4j
This documentation is licensed under the Creative Commons License (Attribution-NoDerivatives 4.0 International).