-
Notifications
You must be signed in to change notification settings - Fork 0
guide logic layer
The logic layer is the heart of the application and contains the main business logic. According to our business architecture we divide an application into business components. The component part (see architecture overview) assigned to the logic layer contains the functional use-cases the business component is responsible for. For further understanding, consult the application architecture.
A component part is accessed through its component part interface. The API of the component part interface has to be business oriented. This means that all parameters and return types of a method have to be business transfer-objects, datatypes (String, Integer, MyCustomerNumber, etc.), or collections of these. The API may only access objects of other business components listed in the (transitive) dependencies of the declaring business component part.
First we create the interface that contains the method(s) with the business operations documented with JavaDoc.
There are two ways of designing a component part interface at the logic layer. Depending on the application’s complexity one of the following approaches should be consistently applied (i.e. you should not use both approaches within the same application).
-
Component Part with Simple Interface
-
Component Part Interface with Use Case Decomposition
For less complex apps with fairly simple component interfaces (even if it contains many methods, e.g. several find methods), you put all methods to be exposed directly into a single interface. The implementation of the component part interface provides all the corresponding methods in one class.
Here is an example of a simple interface:
/**
* ... StaffManagement.java
*/
public interface StaffManagement {
/**
* @param id the {@link StaffMemberEto#getId() ID} of the requested staff member.
* @return The {@link StaffMemberEto} with the given <code>id</code> or {@code null} if no such object exists.
*/
StaffMemberEto findStaffMember(Long id);
/**
* @param login The {@link StaffMemberEto#getName() login} of the requested staff member.
* @return The {@link StaffMemberEto} with the given <code>login</code> or {@code null} if no such object exists.
*/
StaffMemberEto findStaffMemberByLogin(String login);
...
}
For complex applications, component part interfaces consisting of many different use cases, it is recommended to further sub-divide it into separate use-case-interfaces to be aggregated in the main component interface. This suits for better maintainability.

The component part interface then extends the available use case interfaces to offer a single interface to the next higher layer, e.g. the service layer. Then, the implementation of the component part interface holds references to all use cases and only delegates method calls. All business logic and data-layer access is performed within the implementations of the use cases. Also, if a use case needs to use functionality of another use case provided by the same layer it will use a reference to the component part interface and not to the use case itself.
/**
* ... Salesmanagement.java
*/
public interface Salesmanagement extends UcChangeTable, UcFindBill, UcFindOrder, UcFindOrderPosition, UcManageBill,
UcManageOrder, UcManageOrderPosition {
}
// ...
/**
* ... UcChangeTable.java
*/
public interface UcChangeTable {
/**
* UseCase to change from one {@link TableEto table} to another. The people sitting at a table are identified by their
* {@link OrderEto order} that has to be provided as argument.
*
* @param orderId the {@link OrderEto order}
* @param newTableId the new {@link TableEto table} to switch to.
*/
void changeTable(long orderId, long newTableId);
}
The implementation of the use case typically needs access to the persistent data. This is done by injecting the corresponding DAO. According to the principle data sovereignty , only DAOs of the same business component may be accessed directly from the use case. For accessing data from other components the use case has to use the corresponding component interface. Further, it shall not expose persistent entities from the persistence layer and has to map them to transfer objects.
Within a use-case implementation, entities are mapped via a BeanMapper to persistent entities. Let’s take a quick look at some of the Ordermanagement methods:
package io.oasp.application.mtsj.ordermanagement.logic.impl;
@Named
@Transactional
public class OrdermanagementImpl extends AbstractComponentFacade implements Ordermanagement {
@Inject
private OrderDao orderDao;
@Override
public OrderCto findOrder(Long id) {
OrderEntity entity = getOrderDao().findOne(id);
OrderCto cto = new OrderCto();
cto.setBooking(getBeanMapper().map(entity.getBooking(), BookingEto.class));
cto.setHost(getBeanMapper().map(entity.getHost(), BookingEto.class));
cto.setOrderLines(getBeanMapper().mapList(entity.getOrderLines(), OrderLineCto.class));
cto.setOrder(getBeanMapper().map(entity, OrderEto.class));
cto.setInvitedGuest(getBeanMapper().map(entity.getInvitedGuest(), InvitedGuestEto.class));
return cto;
}
As you can see, provided entities are mapped to corresponding business objects (here BookingEto.class). These business objects are simple POJOs (Plain Old Java Objects) and stored in:
<package-name-prefix>.<domain>.<application-name>.<component>.api.
The mapping process of these entities and the declaration of the AbstractLayerImpl class are described here. For every business object there has to be a mapping entry in the config/app/common/dozer-mapping.xml file. For example, the mapping entry of a BookingEto to a Booking looks like this:
<mapping>
<class-a>io.oasp.gastronomy.restaurant.tablemanagement.logic.api.TableEto</class-a>
<class-b>io.oasp.gastronomy.restaurant.tablemanagement.persistence.api.entity.Table</class-b>
</mapping>
Below, a class diagram illustrating the pattern is shown (here: the StaffManagement
business component):

As the picture above illustrates, the necessary DAO entity to access the database is provided by an abstract class. Use Cases that need access to this DAO entity, have to extend that abstract class. Needed dependencies (in this case the staffMemberDao) are resolved by Spring, see here. For the validation (e.g. to check if all needed attributes of the StaffMember have been set) either Java code or Drools, a business rule management system, can be used.
Entities have to be detached for the reasons of data sovereignty, if entities are passed among components or layers (to service layer). For further details see Bean-Mapping. Therefore we are using transfer-objects (TO) with the same attributes as the entity that is persisted. The packages are:
Persistence Entities |
<package-name-prefix>.<domain>.<application-name>.<component>.persistence.api.entity |
Transfer Objects(TOs) |
<package-name-prefix>.<domain>.<application-name>.<component>.logic.api |
This mapping is a simple copy process. So changes out of the scope of the owning component to any TO do not directly affect the persistent entity.
The logic layer is the heart of the application. It is also responsible for authorization and hence security is important here. Every method exposed in an interface needs to be annotated with an authorization check, stating what role(s) a caller must provide in order to be allowed to make the call. The authorization concept is described here.
A security threat are Insecure Direct Object References. This simply gives you two options:
-
avoid direct object references at all
-
ensure that direct object references are secure
Especially when using REST, direct object references via technical IDs are common sense. This implies that you have a proper authorization in place. This is especially tricky when your authorization does not only rely on the type of the data and according static permissions but also on the data itself. Vulnerabilities for this threat can easily happen by design flaws and inadvertence. Here is an example from our sample application:
We have a generic use-case to manage BLOBs. In the first place it makes sense to write a generic REST service to load and save these BLOBs. However, the permission to read or even update such BLOB depend on the business object hosting the BLOB. Therefore, such a generic REST service would open the door for this OWASP A4 vulnerability. To solve this in a secure way, you need individual services for each hosting business object to manage the linked BLOB and have to check permissions based on the parent business object. In this example the ID of the BLOB would be the direct object reference and the ID of the business object (and a BLOB property indicator) would be the indirect object reference.
This documentation is licensed under the Creative Commons License (Attribution-NoDerivatives 4.0 International).