4.1. Domain Layer Implementation¶
Caution
This version is already obsolete. Please check the latest guideline.
Index
- Roles of domain layer
- Flow of development of domain layer
- Implementation of Entity
- Implementation of Repository
- Implementation of Service
- Regarding transaction management
- Appendix
- Tips
4.1.1. Roles of domain layer¶
Domain layer implements business logicto be provided to the application layer.
Implementation of domain layer is classified into the following.
S.No. Classification Description 1. Creation of classes (Entity class) to hold business data. 2. Implementation of the methods to operate on business data. These methods are provided to Service classes.These are in particular the CRUD operations on Entity object. 3. Implementation of the methods for executing business logic. These methods are provided to the application layer.Business data required by the business logic is fetched as the Entity object through the Repository.
This guideline recommends the structure of creating Entity classes and Repository for the following reasons.
- By splitting the overall logic into business logic (Service) and the logic to access business data, the implementation scope of business logic gets limited to the implementation of business rules,
- Access logic to business data is standardized by consolidating the operations of business data in the Repository.
Note
Though this guideline recommends a structure to create Entity classes and Repository, it is not mandatory to perform development in this structure.
Decide a structure by taking into account the characteristics of the application as well as the project (structure of development team and development methodology).
4.1.2. Flow of development of domain layer¶
S.No. Team in-charge Description (1) Common development team Common development team designs and creates Entity classes. (2) Common development team Common development team works out access pattern for the Entity classes extracted in (1) and designs methods of Repository interface.Common development team should implement the methods to be shared by multiple development teams. (3) Common development team Common development team provides Entity classes and Repository created in (1) and (2) to the business application development team.At this time, it requests each business application development team to implement the Repository interface. (4) Business application development team Business application development team takes charge of the implementation of Repository interface. (5) Business application development team Business application development team develops Service interface and Service class using the Entity class and Repository provided bythe common development team and the Repository implementation class created by the team itself.Warning
A system having a large development scope is often developed by assigning the application to multiple teams. In that case, it is strongly recommended to provide a common team to design Entity classes and Repository.
When there is no common team, O/R Mapper(Mybatis) should be called from Service directly without creating Entity classes and Repository.
4.1.3. Implementation of Entity¶
4.1.3.1. Policy of creating Entity class¶
S.No. Method Supplementary 1. Create Entity class for each table. However, Entity class is not required for mapping tables which represent the the relationship between the tables.Further, when the tables are not normalized, Entity class for each table rule may not be applible. Refer to the Warning as well as Note outside this tablefor the approach related to not-normalized tables. 2. When there is a FK (Foreign Key) in the table, the Entity class of FK destination table must be defined as one of the properties of this Entity. When there is 1:N relationship with FK destination table, use eitherjava.util.List<E>
orjava.util.Set<E>
.The Entity corresponding to the FK destination table is called as the related Entity in this guideline. 3. Treat the code related tables asjava.lang.String
rather than as an Entity. Code related tables are to manage the pairs of code value and name.When there is a need to bifurcate the process as per code values,enum
class corresponding to code value should be created and it must be defined as property.
Warning
When table is not normalized, check whether to use the method of creating the Entity classes and Repository by considering the following points. Since the unnormalized tables do not have good compatibility with JPA, it is better not to use JPA.
Creating an appropriate Entity class may often not be possible because of increased difficulty in creating entities if the tables are not normalized.In addition, efforts to create an Entity classes also increases.Two viewpoints must be taken into consideration here. Firstly “Can we assign an engineer who can perform normalization properly?” and secondly “Is it worth taking efforts for creating normalized Entity classes?”. If the tables are not normalized, the logic to fill the gap of differences between the Entity class and structure of table is required in data access.Here the viewpoint to be considered is, “Is it worth taking efforts to fill the gap of differences between the Entity class and structure of table ?”.The method of creating Entity classes and Repository is recommended; however, the characteristics of the application as well as the project (structure of development team and development methodology) must also be taken into account.
Note
If you want to operate on business data with normalized Entity classes - even if the tables are not normalized - it is recommended to use Mybatis as an implementation of RepositoryImpl of the infrastructure layer.
Mybatis is the O/R Mapper developed to map the SQL with object and not to map the database table record with object. So depending on the implementation of SQL, mapping to the object independent of table structure is possible.
4.1.3.2. Example of creating Entity class¶
4.1.3.2.1. Table structure¶
The table structure is as given below:
S.No. Classification Table name Description (1) Transaction related t_order Table to store orders. 1 record is stored for 1 order. (2) t_order_item Table to store the products purchased in 1 order. Record of each product is stored when multiple products are purchased in 1 order. (3) t_order_coupon Table to store the coupon used in a single order. Record of each coupon is stored when multiple coupons are used in 1 order. No record is stored when coupon is not used. (4) Master related m_item Master table to define products. (5) m_category Master table to define product category. (6) m_item_category Master table to define the category of the product. Mapping between product and category is maintained. Model where 1 product belongs to multiple categories. (7) m_coupon Master table to define coupons. (8) Code related c_order_status Code table to define order status.
4.1.3.2.2. Entity structure¶
If Entity classes are created with the help of policy defined by the above table, it results into the following structure.
S.No. Class name Description (1) Order Entity class indicating 1 record of t_order table.MultipleOrderItem
andOrderCoupon
are stored as the related Entity. (2) OrderItem Entity class indicating 1 record of t_order_item table.Item
is stored as the related Entity. (3) OrderCoupon Entity class indicating 1 record of t_order_coupon table.Coupon
is stored as the related Entity. (4) Item Entity class indicating 1 record of m_item table.MultipleCategory
are stored as the related Entity. The association betweenItem
andCategory
is done using m_item_category table. (5) Category Entity class indicating 1 record of m_category table. (6) ItemCategory Entity class is not created since m_item_category table is the mapping table to store the relationship between m_item table and m_category table. (7) Coupon Entity class indicating 1 record of m_coupon table. (8) OrderStatus Entity class is not created since c_order_status table is code table.
As it can be observed from the above entity diagram, it might first seem that Order class is the only main entity class in the shopping site application; however, there are other main entity class as well other than Order class.
Below is the classification of main Entity classes as well as Entity class which are not main.
The following 4 Entities are treated as the main Entity for creating shopping site application.
S.No. Entity class Reasons for treating as the main Entity. (1) Order class It is one of the most important Entity class in the shopping site.Order class is the Entity indicating the order itself and a shopping site cannot be created without the Order class. (2) Item class It is one of the most important Entity class in the shopping site.Item class is the Entity indicating the products handled in the shopping site and a shopping site cannot be created without Item class. (3) Category class Product categories are displayed usually on the top page or as a common menu in shopping sites. In such shopping sites, Category becomes a main entity. Usually operations like ‘search category list’ can be expected. (4) Coupon class Often discounts through coupons are offered in the shopping sites as a measure of promoting sales of the products.In such shopping sites, Coupon becomes a main entity. Usually operations like ‘search coupon list’ can be expected.
The following are not main Entities for creating shopping site application.
S.No. Entity class Reason of not treating Entity as main Entity (5) OrderItem class This class indicates 1 product purchased in 1 order and exists only as the related Entity of Order class.So OrderItem class should not be considered as main Entity. (6) OrderCoupon This class indicates 1 coupon used in 1 order and exists only as the related Entity of Order class.So, OrderCoupon class should not be considered as main Entity.
4.1.4. Implementation of Repository¶
4.1.4.1. Roles of Repository¶
Repository has following 2 roles.
- To provide to Service, the operations necessary to control Entity lifecycle (Repository interface).The operations for controlling Entity lifecycle are CRUD operations.
- To provide persistence logic for Entity (implementation class of Repository interface).Entity object should persist irrespective of the lifecycle (start and stop of server) of application.Mostly relational database is the permanent destination of Entity. However, NoSQL database, cache server, external system and file (shared disk) can also be the permanent destination.The actual persistence processing is done using O/R Mapper API.This role is implemented in the RepositoryImpl of the infrastructure layer. Refer to Implementation of Infrastructure Layerfor the details.
4.1.4.2. Structure of Repository¶
Repository consists of Repository interface and RepositoryImpl and performs the following roles.
S.No. Class(Interface) Role Description (1) Repository interface Defines methods to control Entity lifecycle required for implementing business logic (Service). Defines methods for CRUD operations of the Entity and is not dependent on persistence layer.Repository interface belongs to the domain layer since it plays the roles of defining the operations on Entity required for implementing business logic (Service). (2) RepositoryImpl Implements the methods defined in Repository interface. Implements CRUD operations of the Entity and is dependent on persistence layer. Performs actual CRUD processes using API that performs persistence provided by Spring Framework, O/R Mapper and middleware.RepositoryImpl belongs to infrastructure layer since it plays the role of implementing the operations defined in Repository interface.Refer to Implementation of Infrastructure Layerfor the implementation of RepositoryImpl.
Note
Is it possible to hide 100% of persistence platform dependent logic from the Service class ?
In some cases it cannot be hidden completely due to constraints of persistence platform and the libraries used to access the platform. As much as possible, platform dependent logic should be implemented in RepositoryImpl instead of Service class. When it is difficult to exclude the platform dependent logic and merits of doing so are less, persistence platform dependent logic can be implemented as a part of business logic (Service) process.
A specific example of this is given here. There are cases when unique constraints violation error is needed to be handled when save method of
org.springframework.data.jpa.repository.JpaRepository
interface provided by Spring Data JPA is called. In case of JPA, there is a mechanism of cache entity operations and SQL is executed when transactions are committed. Therefore, since SQL is not executed even if save method of JpaRepository is called, unique constraints violation error cannot be handled in logic. There is a method (flush method) to reflect cached operations as means to explicitly issue SQLs in JPA. saveAndFlush and flush methods are also provided in JpaRepository for the same purpose. Therefore, when unique constraints violation error needs to be handled using JpaRepository of Spring Data JPA, JPA dependent method (saveAndFlush or flush) must be called.Warning
The most important purpose of creating Repository is not to exclude the persistence platform dependent logic from business logic. The most important purpose is to limit the implementation scope of business logic (Service) to the implementation of business rules. This is done by separating the operations to access business data in Repository. As an outcome of this, persistence platform dependent logic gets implemented in Repository instead of business logic (Service).
4.1.4.3. Creation of Repository¶
Repository must be created using the following policy only.
S.No. Method Supplementary 1. Create Repository for the main Entity only. This means separate Repository for operations of related Entity is not required.However, there are case when it is better to provide Repository for the related Entity in specific applications (for example,application having high performance requirements etc). 2. Place Repository interface and RepositoryImpl in the same package of domain layer. Repository interface belongs to domain layer and RepositoryImpl belongs to infrastructure layer. However,Java package of RepositoryImpl can be same as the Repository interface of domain layer. 3. Place DTO used in Repository in the same package as Repository interface. For example, DTO to store search criteria or summary DTO for that defines only a few items of Entity.
4.1.4.4. Example of creating Repository¶
4.1.4.4.1. Structure of Repository¶
Entity class used in the explanation of Example of creating Entity classis used as an example, the resulting configuration is as follows:
4.1.4.5. Definition of Repository interface¶
4.1.4.5.1. Creation of Repository interface¶
An example of creating Repository interface is introduced below.
SimpleCrudRepository.java
This interface provides only simple CRUD operations.Method signature is created by referring toCrudRepository
interface andPagingAndSortingRepository
provided by Spring Data.public interface SimpleCrudRepository<T, ID extends Serializable> { // (1) T findOne(ID id); // (2) boolean exists(ID id); // (3) List<T> findAll(); // (4) Page<T> findAll(Pageable pageable); // (5) long count(); // (6) T save(T entity); // (7) void delete(T entity); }
S.No. Description (1) Method to fetch the Entity object of specified ID. (2) Method to determine if the Entity of specified ID exists or not. (3) Method to retrieve the list of all Entities. In Spring Data, it wasjava.util.Iterable
. Here as a sample, it is set tojava.util.List
. (4) Method to fetch collection of Entity objects corresponding to the specified pagination information (start position, record count, sort information).Pageable
andPage
are the interfaces provided by Spring Data. (5) Method to fetch total number of Entity objects. (6) Method to save (create, update) the specified Entity collection. (7) Method to delete the specified Entity.
TodoRepository.java
An example of creating Repository of Todo Entity, which was created in tutorial, on the basis of
SimpleCrudRepository
interface created above is shown below.// (1) public interface TodoRepository extends SimpleCrudRepository<Todo, String> { // (2) long countByFinished(boolean finished); }
S.No. Description (1) TodoRepository interface is created by specifying Todo entity in the generic type parameter “T” andString class in the generic type parameter “ID”. (2) Methods not provided bySimpleCrudRepository
interface are added in this interface.In this case, “Method for acquiring count of Todo entity objects for which specified tasks have been finished” is added.
4.1.4.5.2. Method definition of Repository interface¶
CrudRepository
and PagingAndSortingRepository
provided by Spring Data for the methods performing general CRUD operations.java.util.Collection
or java.util.List
) interfaces which can be handled in a better way in logic are better than java.lang.Iterable
.
S.No. Types of methods Rules
Method for searching a single record
- Method name beginning with findOneByto indicate that this method fetches a single record that matches with the condition.
- In the method name after “findOneBy”, physical or logical name of the field used as search condition must be specified. Hence, the method name must be such that it becomes possible to estimate “the kind of entity that can be fetched using this method”.
- There must be an argument for each search condition. However, when there are many conditions, DTO containing all search conditions can be provided.
- Return value must be Entity class.
Method for searching multiple records
- Method name beginning with findAllByto indicate that this method fetches all the records that matches with the condition.
- In the method name after “findAllBy”, physical or logical name of the field used as search condition must be specified. Hence, the method name must be such that it becomes possible to estimate “the kind of entity that can be fetched using this method”.
- There must be an argument for each search condition. However, when there are many conditions, DTO containing all search conditions can be provided.
- Return value must be collection of Entity class.
Method for searching multiple records with pagination
- Method name beginning with findPageByto indicate that this method fetches pages that matches with the condition.
- In the method name after “findPageBy”, physical or logical name of the field used as search condition must be specified. Hence, the method name must be such that it becomes possible to estimate “the kind of entity that can be fetched using this method”.
- There must be an argument for each search condition. However, when there are many conditions, DTO containing all search conditions can be provided.
Pageable
provided by Spring Data should be the interface for pagination information (start position, record count, sort information).- Return value should be
Page
interface provided by Spring Data.
Count related method
- Method name beginning with countBy to indicate that this method fetches count of Entities which matches with the condition.
- Return value must be long type.
- In the method name after “countBy”, physical or logical name of the field used as search condition must be specified. Hence, the method name must be such that it becomes possible to estimate “the kind of entity that can be fetched using this method”.
- There must be an argument for each search condition. However, when there are many conditions, DTO containing all search conditions can be provided.
Method for existence check
- Method name beginning with existsBy to indicate that this method checks the existence of Entity which matches with the condition.
- In the method name after “existsBy”physical or logical name of the field used as search condition must be specified. Hence, the method name must be such that it becomes possible to estimate “the kind of entity that can be fetched using this method”.
- There must be an argument for each search condition. However, when there are many conditions, DTO containing all search conditions can be provided.
- Return value must be boolean type.
Note
In case of methods related to update processing, it is recommended to construct methods in the same way as shown above. “find” in the method name above can be replaced by “update” or “delete”.
Todo.java
(Entity)
public class Todo implements Serializable { private String todoId; private String todoTitle; private boolean finished; private Date createdAt; // ... }
TodoRepository.java
public interface TodoRepository extends SimpleCrudRepository<Todo, String> { // (1) Todo findOneByTodoTitle(String todoTitle); // (2) List<Todo> findAllByUnfinished(); // (3) Page<Todo> findPageByUnfinished(); // (4) long countByExpired(int validDays); // (5) boolean existsByCreateAt(Date date); }
S.No. Description (1) Example of method that fetches TODO objects whose title matches with specified value (TODO in which todoTitle=[argument value]).Physical name(todoTitle) of condition field is specified after findOneBy. (2) Example of method that fetches unfinished TODO objects (TODO objects where finished=false).Logical condition name is specified after findAllBy. (3) Example of method that fetches pages of unfinished TODOs (TODO objects where finished=false).Logical condition name is specified after findPageBy. (4) Example of method that fetches count of TODO objects for which the finish deadline has already passed (TODO for which createdAt < sysdate - [finish deadline in days] && finished=false).Logical condition name is specified after countBy. (5) Example of method that checks whether a TODO is created on a specific date (createdAt=specified date).Physical name (createdAt) is specified after existsBy.
4.1.4.5.3. Creation of RepositoryImpl¶
Refer to Implementation of Infrastructure Layerfor the implementation of RepositoryImpl.
4.1.5. Implementation of Service¶
4.1.5.1. Roles of Service¶
Service plays the following 2 roles.
- Provides business logic to Controller.Business logic consists of create, update, consistency check etc of business data as well as all the processes related to business logic.Create and update process of business data should be delegated to Repository(or O/R Mapper) and service should be limited to implementation of business rules.
Note
Regarding distribution of logic between Controller and Service
In this guideline, the logic to be implemented by Controller and Service should be as per the rules given below.
- For the data requested from the client, single item check and correlated item check is to be performed in Controller (Bean Validation or Spring Validator).
- Conversion processes (Bean conversion, Type conversion and Format conversion) for the data to be passed to Service, must be performed in Controller instead of Service.
- Business rules should be implemented in Service.Access to business data is to be delegated to Repository or O/R Mapper.
- Conversion processes (Type conversion and Format conversion) for the data received from Service (data to respond to the client), must be performed in Controller (View class etc).
- Declare transaction boundary.Declare transaction boundary when business logic is performing any operation which requires ensuring data consistency (mainly data update process).Even in case of logic that just read the data, often there are cases where transaction management is required due to the nature of business requirements. In such cases, declare transaction boundary.Transaction boundary must be set in Service layer as a principle rule. If it is found to be set in application layer (Web layer), there is a possibility that the extraction of business logic hasnot been performed correctly.
Refer to Regarding transaction managementfor details.
4.1.5.2. Structure of Service class¶
@Service
annotation is defined as Service or SharedService class.
S.No. Class Role Notes related to dependency relationship
Service class Provides business logic to the specific Controller.Service class methods must not implement logic that need to be reused.
- It is prohibited to call a method of Service class from another Service class method (Figure 1-1).For shared logic, create SharedService class.
- Method of Service class can be called from multiple Controllers (Figure 1-2). However, it must be created for each controller when processing is to be branched based on the calling controller.In such a scenario, create a method in SharedService class and call that method from the individual Service class methods.
2 SharedService class Provides shared (reusable) logicfor multiple Controllers and Service classes.
- Methods of other SharedService classes can be called from a SharedService (Figure 2-1). However, Calling hierarchy should not become complicated. If calling hierarchy becomes complicated, there is a risk of reduction in maintainability.
- Methods of SharedService classes can be called from Controller (Figure 2-2). However, it can be only be done if there is no problem from transaction management perspective.If there is a problem from transaction management perspective, first create a method in Service class and implement transaction management in this method.
- It is prohibited to call methods of Service class from SharedService (Figure 2-3).
4.1.5.2.2. Reason for prohibiting the calling of other Service classes from Service class¶
S.No. Situations that can occur
The logic that must be implemented in the calling service class, gets implemented in the called service class for reasons like “having the logic at a single location” etc.As a result, arguments for identifying the caller, get added to the method easily; Ultimately, the logic is incorrectly abstracted out as shared logic (like utilities). It results into a modular structure without much insight.
If the stack patterns or stack of services calling each other is large in number, understanding the impact of modifications in source-code due to change in specifications or bug fixes, becomes difficult.
4.1.5.2.3. Regarding interface and base classes to limit signature of method¶
Note
In large scale development, there are situations where not every single developer is highly skilled or situations like having consistency in developemnt of business logic considering maintainability after servicing. In such situations, limiting the signature through interfaces can be an appropriate decision.
In this guideline, we do not specifically recommended to create interface to limit signature; however, type of architecture must be selected on the basis of characteristics of the project. decide the type of architecture taking into account the project properties.
4.1.5.3. Patterns of creating service class¶
There are mainly 3 patterns for creating Service.
S.No. Unit Creation method Description
For each Entity Create Service paired with the main Entity. Main Entity is in other words, business data. If the application is to be designed and implemented with focus on business data, then Service classes should be created in this way.If service is created in this way, business logic will also be created for each entity and it will become to extract shared logic.However, if Service is created using this pattern, its affinity is not so good with the type of application which has to be developedby introducing a large number of developers at the same time.It can be said that the pattern is suitable when for the developing small or medium sized application.
For each use-case Create Service paired with the use-case. If the application is to be designed and implemented with focus on events on the screen, Service should be created in this way.If the Service is created using this pattern, it is possible to assign a person to each use case; hence, its affinity is good withthe type of application which has to be developed by introducing a large number of developer at the same time.On the other hand, if Service is created using this pattern, shared logic within use case can be extracted to a single location;however, shared logic which spans across multiple use-case might not get extracted to a single location.When it is very important to have extract shared logic out to a single location, it becomes necessary devise measures like havinga separate team to look after designing shared components of business logic that span across multiple use cases.3 For each event Create Service paired with the events generated from screen. If the application is to be designed and implemented with focus on events on the screen and BLogic class is auto-generated using TERASOLUNA ViSC, Service should be created in this way.In this guideline, the Service class created using this pattern is calledBLogic
.The characteristics of the application if creating Service using this pattern are basically same as those when creating Service for each use case.However, in this case extracting shared logic out of business logic might get more difficult compared to creation of Service for each use-case (Pattern No. 2).In this guideline, pattern of creating Service for each event is not specifically recommended. However, in large scale development, creating Service using thispattern can be considered as one of the options with the view of having consistency in development style of business logic from maintainability point of view.Warning
The pattern of Service creation must be decided by taking into account the features of application to be developed and the structure of development team.
It is not necessary to narrow down to any one pattern out of the 3 indicated patterns. Creating Services using different patterns randomly should be avoided for sure; however, patterns can be used in combinations, if policy of usage of patterns in certain specific conditions has been well-thought decision and has been directed by the architect. For example, the following combinations are possible.
[Example of usage of patterns in combination]
- For the business logic very important to the whole application, create as SharedService class for each Entity.
- For the business logic to be processed for the events from the screen, create as Service class for each Controller.
- In the Service class for each controller, implement business logic by calling the sharedService as and when required.
Tip
BLogic is generated directly from design documents when using “TERASOLUNA ViSC”.
4.1.5.3.1. Image of Application development - Creating Service for each Entity -¶
Following is the iamge of application development when creating a Service for each Entity.
Note
An example of a typical application in which a Service is created for each Entity is a REST application. REST application provides CRUD operations (POST, GET, PUT, DELETE of HTTP) for published resources on HTTP. Most of the times, the resources published on HTTP are business data (Entity) or part of business data (Entity), they have good compatibility with the pattern of creating Service for each Entity.
In case of REST application, most of the times, use-cases are also extracted on a “per Entity” basis. Hence, the structure is similar to the case when Service is created for on a “per use-case” basis.
S.No. Description (1) Implement Service by assigning a person for each Entity.If there is no specific reason, it is desirable that Controller must also be created for each Entity and must be developed by the same developer who created the Service class. (2) Implement SharedService if there is shared logic between multiple business logics.In the above figure, different person is assigned as the incharge. However, he may be the same person as (1) depending per the project structure.
4.1.5.3.2. Image of Application development - Creating Service for each use case¶
S.No. Description (1) Implement Service by assigning a person for each use-case.If there is no specific reason, it is desirable that Controller must also be created for each use-case and must be developed by the same developer who created the Service class. (2) Implement SharedService if there is shared logic between multiple business logics.In the above figure, different person is assigned as the incharge. However, he may be the same person as (1) depending per the project structure.Note
With an increase in the size of the use-cases, the development scope of a person increases. At such a point of time, it becomes difficult to divide the work of this use-case with other developers. In case of application which has to be developed by introducing a large number of developer at the same time, the use-case can be further split into finer use-cases and which can then be allocated to more number of developers.
S.No. Description (1) Divide the use-case into finer processes which make-up the complete use-case. Assign each fine process to a developer. Each developer creates the Service for assigned process.Note that the processes here are operations like search, create, update, delete etc. and these processes do not have a direct mapping to the processing required to be done foreach event generated on screen.For example, if it the event generated on screen is “Update”, it includes multiple finer processes such as “Fetching the data to be updated”, “Compatibility check of update contents” etc.If there is no specific reason, it is desirable that Controller must also be created for each of these finer processes and must be developed by the same developer who creates the Service class.Tip
In some projects, “group of use-cases” and “use-cases” are used in place of “use-case” and “processes” used in this guideline.
4.1.5.3.3. Image of Application development - Creating Service for each use event¶
Following is the iamge of application development when creating a Service(BLogic) for each event.
S.No. Description (1) Implement Service(BLogic) by assigning a person for each event.Above example is an extreme case where separate developer is assigned for each service(BLogic).In reality, a single person must be assigned for a use-case. (2) If there is no specific reason, controller also should be created on “per use-case” basis. (3) Even if the separate Service(BLogic) is created for each event, it is recommended that same person is the in-charge of the complete use-case. (4) Implement in SharedService to share the logic with multiple business logics.In the above figure, different person is assigned as the incharge. However, he may be the same person as (1) depending per the project structure.Note
With an increase in the size of the use-cases, the development scope of a person increases. At such a point of time, it becomes difficult to divide the work of this use-casewith other developers. In case of application which has to be developed by introducing a large number of developer at the same time, the use-case can be further split into finer use-cases and which can then be allocated to more number of developers.
S.No. Description (1) Divide the use-case into finer processes which make-up the complete use-case. Assign each fine process to a developer. Each developer creates the Service for assigned process.Note that the processes here are operations like search, create, update, delete etc. and these processes do not have a direct mapping to the processing required to be done foreach event generated on screen.For example, if it the event generated on screen is “Update”, it includes multiple finer processes such as “Fetching the data to be updated”, “Compatibility check of update contents” etc.If there is no specific reason, it is desirable that Controller must also be created for each of these finer processes and must be developed by the same developer who creates the Service class.
4.1.5.4. Creation of Service class¶
4.1.5.4.1. Methods of creating Service class¶
Below are the points to be taken care of while creating Service class.
- Creation of Service interface
public interface CartService { // (1) // omitted }
S.No. Description (1) It is recommended to create Service interface.By providing an interface, it is possible to execute the method published as Service explicitly.
Note
Merits from architecture perspective
- If interface is there, When using AOP, Dynamic proxies functionality of standard JDK is used. In case of no interface, CGLIB included in Spring Framework is used. In case of CGLIB there are certain restrictions like “Advice can not be applied on final methods” etc. Refer to Spring Reference Documentfor details.
- It becomes easier to create a stub of business logic. When application layer and domain layer are developed in parallel using different development teams, stubs of Service are required. When there is a need to create stubs, it is recommended to have interface .
- Creation of Service class
@Service // (1) @Transactional // (2) public class CartServiceImpl implements CartService { // (3) (4) // omitted }<context:component-scan base-package="xxx.yyy.zzz.domain" /> <!-- (1) -->
S.No. Description (1) Add @Service annotation to class.By adding the above annotation, bean definition in configuration file is not required.Specify package for component scanning inbase-package
attribute of <context:component-scan> element.In case of this example, all the classes in “xxx.yyy.zzz.domain” is registered in container. (2) Add @Transactional annotation to class.By adding the above annotation, transaction boundary is set for to the all the methods of the Service class.value
attribute should be specified as required.Refer to Information required for “Declarative transaction management”for details. (3) Consider interface name as XxxService and class name as XxxServiceImpl.Any naming conventions can be used. However, it is recommended to use distinguishable naming conventions for Service class and SharedService class. (4) Service class must not maintain state. Register it in container as bean of singleton scope .Objects (POJO such as Entity/DTO/VO) and values (primitive type, primitive wrapper class) where state changes in each thread should not be maintained in class level fields.Setting scope to any value other than singleton (prototype, request, session) using@Scope
annotation is also prohibited.
Note
Reason for adding @Transactional annotation to class
Transaction boundary is required only for the business logic that updates the database. However, it is recommended to apply the annotation at class level to prevent bugs due to skipped annotation. However, defining
@Transactional
annotation only at required places (methods which update the database) is also fine.Note
Reason to prohibit non-singleton scopes
- prototype, request, session are the scopes for registering bean that maintains state. Hence they must not be used in Service class.
- When scope is set to request or prototype, performance is affected as the bean generation frequency is high in DI container.
- When scope is set to request or session, it cannot be used in non Web applications (for example, Batch application).
4.1.5.4.2. Creation of methods of Service class¶
Below are the points to be taken care of while writing methods of Service class.
- Creation of method of Service interface
public interface CartService { Cart createCart(); // (1) (2) Cart findCart(String cartId); // (1) (2) }
- Creation of methods of Service class
@Service @Transactional public class CartServiceImpl implements CartService { @Inject CartRepository cartRepository; public Cart createCart() { // (1) (2) Cart cart = new Cart(); // ... cartRepository.save(cart); return cart; } @Transactional(readOnly = true) // (3) public Cart findCart(String cartId) { // (1) (2) Cart cart = cartRepository.findByCartId(cartId); // ... return cart; } }
S.No. Description (1) Create a method of Service class for each business logic. (2) Define methods in Service interface and implement business logic in its implementation class. (3) Add @Transactional annotation for changes to default transaction definition (class level annotation).Attributes should be specified as per the requirement.Refer to Information required for “Declarative transaction management” for details.
Warning
Regarding transaction definition of business logic that just reads the database and does not update values
Transaction management for reference queries can be applied by through
@Transactional(readOnly = true)
. However, for JPA, there is no need to specify “readOnly = true”. Refer to “Listing 7. Using read-only with REQUIRED propagation mode - JPA” of IBM DeveloperWorks articlefor details.Note
Defining transaction when a new transaction is required to be started
Set
@Transactional(propagation = Propagation.REQUIRES_NEW)
to start a new transaction without participating in the transaction of the caller method.
4.1.5.4.3. Regarding arguments and return values of methods of Service class¶
The below points must be considered for arguments and return values of methods of Service class.
java.io.Serializable
) must be used for arguments and return values of Service class.Typical arguments and return values of the methods are as follows.
- Primitive types (
int
,long
)- Primitive wrapper classes (
java.lang.Integer
,java.lang.Long
)- java standard classes (
java.lang.String
,java.util.Date
)- Domain objects (Entity, DTO)
- Input/output objects (DTO)
- Collection (implementation class of
java.util.Collection
) of above types- void
- etc …
Note
Input/Output objects
- Input object indicates the object that has all the input values required for executing Service method.
- Output object indicates the object that has all the execution results (output values) of Service method.
If business logic(BLogic class) is generated using “TERASOLUNA ViSC” then, input and output objects are used as argument and return value of the of BLogic class.
Values that are forbidden as arguments and return values are as follows.
- Objects (
javax.servlet.http.HttpServletRequest
,javax.servlet.http.HttpServletResponse
,javax.servlet.http.HttpSession
,org.springframework.http.server.ServletServerHttpRequest
) which are dependent on implementation architecture of application layer (Servlet API or web layer API of Spring).- Model(Form, DTO) classes of application layer
- Implementation classes of
java.util.Map
Note
Reason for prohibition
- If objects depending on implementation architecture of application layer are allowed, then application layer and domain layer get tightly coupled.
java.util.Map
is too generalized. Using it for method arguments and return values makes it difficult to understand what type of object is stored inside it. Further, since the values are managed using keys, the following problems may occur.
- Values are mapped to a unique key and hence cannot be retrieved by specifying a key name which is different from the one specified at the time of inserting the value.
- When key name has to be changed, it becomes difficult to determine the impacted area.
How to sharing the same DTO between the application layer and domain layer is shown below.
- DTO belonging to the package of domain layer can be used in application layer.
Warning
Form and DTO of application layer should not be used in domain layer.
4.1.5.6. Implementation of logic¶
Implementation in Service and SharedService is explained here.
Service and SharedService has implementation of logic related to operations such as data fetch, update, consistency check of business data and implementation related to business rules.
Example of a typical logic is explained below.
4.1.5.6.1. Operate on business data¶
Refer to the following for the examples of data (Entity) fetch and update.
- When using JPA, [coming soon] Database Access (JPA)
- When using Mybatis2, [coming soon] Database Access (MyBatis2)
4.1.5.6.2. Returning messages¶
Note
Regarding resolving message
In service, instead of the actual message the information required for building the message (message code, message insert value) is resolved.
Refer to the following for detailed implementation method.
4.1.5.6.3. Returning warning message¶
org.terasoluna.fw.common.message.ResultMessages
) is provided as common library. When the class provided in common library- Creation of DTO
public class OrderResult implements Serializable { private ResultMessages warnMessages; private Order order; // omitted }
Implementation of method of Service class
Following is an example of implementation of displaying a warning message. The message is “Products may not be delivered together since the order includes products which are not available right now”.
public OrderResult submitOrder(Order order) { // omitted boolean hasOrderProduct = orderRepository.existsByOrderProduct(order); // (1) // omitted Order order = orderRepository.save(order); // omitted ResultMessages warnMessages = null; // (2) if(hasOrderProduct) { warnMessages = ResultMessages.warn().add("w.xx.xx.0001"); } // (3) OrderResult orderResult = new OrderResult(); orderResult.setOrder(order); orderResult.setWarnMessages(warnMessages); return orderResult; }
S.No. Description (1) When the order includes products which are not available right now, sethasOrderProduct
totrue
. (2) In the above example, when the order includes products which are not available right now, a warning message occurs. (3) In the above example, the registeredOrder
object and warning message are returned by storing objects in a DTO calledOrderResult
.
4.1.5.6.4. Notifying business error¶
- When reservation date exceeds deadline while making tour reservation
- When the product is out of stock at the time of placing an order
- etc …
org.terasoluna.fw.common.exception.BusinessException
) is provided as common library.Note
Reason for considering business exception as an unchecked exception
Since business exceptions need to be handled in controller class, they can be configured as checked exception. However in this guideline, it is recommended that business exception be subclass of unchecked exception (
java.lang.RuntimeException
). By default, if there is a RuntimeException, transaction will be rollbacked. Hence, doing this will prevent leaving a bug in the source-code due to inadequate settings of @Transactional annotation. Obviously, if settings are changed such that transaction rollbacks even in case checked exceptions, business exception can be configured as subclass of checked exceptions.
// omitted if(currentDate.after(reservationLimitDate)) { // (1) throw new BusinessException(ResultMessages.error().add("e.xx.xx.0001")); } // omitted
S.No. Description (1)Business exception is thrown since reservation date is past the deadline at the time of making reservation.
Refer to Exception Handlingfor the details of entire exception handling.
4.1.5.6.5. Notifying system error¶
- When master data, directories and files that should already exist, do not exist
- When a checked exception generated by a library method is caught and this exception indicates abnoraml system state.
- etc …
org.terasoluna.fw.common.exception.SystemException
) is provided as common library.@Transactinal
annotation is set to java.lang.RuntimeException
by default.ItemMaster itemMaster = itemMasterRepository.findOne(itemCode); if(itemMaster == null) { // (1) throw new SystemException("e.xx.fw.0001", "Item master data is not found. item code is " + itemCode + "."); }
S.No. Description (1)System exception is thrown since master data that should already exist does not exist. Example of case when system error is detected in logic)
Example that throws system exception while catching IO exception while copying the file is shown below.
// ... try { FileUtils.copy(srcFile, destFile); } catch(IOException e) { // (1) throw new SystemException("e.xx.fw.0002", "Failed file copy. src file '" + srcFile + "' dest file '" + destFile + "'.", e); }
S.No. Description (1) System exception that is classified into invalid system state is thrown by the library method.The exception generated by library must be passed to system exception class as cause exception.If cause exception is lost, error occurrence location and basic error cause can not be traced from the stacktrace.
Note
Regarding handling of data access error
When data access error occurs in Repository and O/R Mapper while executing business logic, it is converted to subclass of
org.springframework.dao.DataAccessException
and thrown. Error can be handled in application layer instead of catching in business logic. However, some errors like unique constraints violation error should be handled in business logic as per business requirements. Refer to [coming soon] Database Access (Common)for details.
4.1.6. Regarding transaction management¶
Transaction management is required in the logic where data consistency must be ensured.
4.1.6.1. Method of transaction management¶
There are various transaction management methods. However, in this guideline, it is recommended to use “Declarative Transaction Management” provided by Spring Framework.
4.1.6.1.1. Declarative transaction management¶
In “Declarative transaction management”, the information required for transaction management can be declared by the following 2 methods.
- Declaration in XML(bean definition file).
- Declaration using annotation (@Transactional) (Recommended).
Refer to Spring Reference Documentfor the details of “Declarative type transaction management” provided by Spring Framework.
Note
Reason for recommending annotation method
- The transaction management to be performed can be understood by just looking at the source code.
- AOP settings for transaction management is not required if annotations are used and so XML becomes simple.
4.1.6.1.2. Information required for “Declarative transaction management”¶
@Transactional
annotation for at class level or method level which are considered as target of transaction management and specify the information required for@Transactional
annotation.
S.No. Attribute name Description 1 propagation Specify transaction propagation method.[REQUIRE]Starts transaction if not started. (default when omitted)[REQUIRES_NEW]Always starts a new transaction.[SUPPORTS]Uses transaction if started. Does not use if not started.[NOT_SUPPORTED]Does not use transaction.[MANDATORY]Transaction should start. An exception occurs if not started.[NEVER]Does not use transaction (never start). An exception occurs if started.[NESTED]save points are set. They are valid only in JDBC.2 isolation Specify isolation level of transaction.Since this setting depends on DB specifications, settings should be decided by checking DB specifications.[DEFAULT]Isolation level provided by DB by default.(default when omitted)[READ_UNCOMMITTED]Reads (uncommitted) data modified in other transactions.[READ_COMMITTED]Does not read (uncommitted) data modified in other transactions.[REPEATABLE_READ]Data read by other transactions cannot be updated.[SERIALIZABLE]Isolates transactions completely.Isolation level of transaction is considered as the parameter related to exclusion control.Refer to [coming soon] Exclusive Control for exclusion control.3 timeout Specify timeout of transaction (seconds).-1 by default (Depends on specifications and settings of DB to be used)4 readOnly Specify Read-only flag of transaction.false by default (Not read-only)5 rollbackFor Specify list of exception classes to rollback transactions.Blank by default(Not specified)6 rollbackForClassName Specify list of exception class names to rollback transactions.Blank by default (Not specified)7 noRollbackFor Specify list of exception classes to commit transactions.Blank by default (Not specified)8 noRollbackForClassName Specify list of exception classes to commit transactions.Blank by default (Not specified)
Note
Location to specify the @Transactional annotation
It is recommended to specify the annotation at the class level or method level of the class. Must be noted that it should not interface or method of interface. Refer to 2nd Tip on Spring Reference Document for reason.
Warning
Default operations of rollback and commit when exception occurs
When rollbackFor and noRollbackFor is not specified, Spring Framework performs the following operations.
- Rollback when unchecked exception of (java.lang.RuntimeException and java.lang.Error) class or its subclass occurs.
- Commit when checked exception of (java.lang.Exception) class or its subclass occurs. (Necessary to note)
Note
Regarding value attributes of @Transactional annotation
There is a value attribute in
@Transactional
annotation. However, this attribute specifies which Transaction Manager to be used when multiple Transaction Managers are declared. It is not required to specify when there is only one Transaction Manager. When it is required to use multiple Transaction Managers, refer to Spring Reference Document.Note
Default isolation levels of main DB are given below.
Default isolation levels of main DB are given below.
- Oracle : READ_COMMITTED
- DB2 : READ_COMMITTED
- PostgreSQL : READ_COMMITTED
- SQL Server : READ_COMMITTED
- MySQL : REPEATABLE_READ
4.1.6.1.3. Propagation of transaction¶
- Controller calls a method of Service class.
At this time, since started transaction does not exist, transaction is started using
TransactionInterceptor
. TransactionInterceptor
calls the method of service class after starting the transaction.- Service class calls a method of
SharedService
. This method is also under transaction control. At this time, though started transaction exists,TransactionInterceptor
participates in the started transaction without starting a new transaction. TransactionInterceptor
calls the method under transaction control after participating in the started transaction.TransactionInterceptor
performs commit or rollback according to result of processing and ends the transaction.
Note
Reason for occurrence of org.springframework.transaction.UnexpectedRollbackException
When propagation method of transaction is set to “REQUIRED”, though there is only one physical transaction, internally Spring Framework creates transaction boundaries.
In case of above example, when a method of SharedService is called, a TransactionInterceptor is started which internally provides transaction control boundary at SharedService level.
Therefore, when an exception (which is set as target of rollback) occurs in SharedService
method, status of transaction is set to rollback (rollback-only) by TransactionInterceptor
.
This transaction now cannot be committed.
Going further, if the Service method tries to commit this transaction due to conflicting settings of rollback target exception between Service method and SharedShared method,
UnexpectedRollbackException
is generated by Spring Framework notifying that there is inconsistency in transaction control settings. When UnexpectedRollbackException is generated, it should be checked that there is no inconsistency in rollbackFor and noRollbackFor settings.
- Controller calls a method of Service class. This method is under transaction control. At this time, since started transaction does not exist, transaction is started by
TransactionInterceptor
(Hereafter, the started transaction is referred as “Transaction A”). TransactionInterceptor
calls the method of service class after transaction (Transaction A) is started.- Service class calls a method of
SharedService
class. At this time, though started transaction (Transaction A) exists, since propagation method of transaction is “REQUIRES_NEW”, new transaction is started byTransactionInterceptor
. (Hereafter, started transaction is referred as “Transaction B”). At this time, “Transaction A” is interrupted and the status changes to ‘Awaiting to resume’. TransactionInterceptor
calls the method of SharedService class after Transaction B is started.TransactionInterceptor
performs commit or rollback according to the process result and ends the Transaction B. At this time, “Transaction A” is resumed and status is changed to Active.TransactionInterceptor
performs commit or rollback according to the process result and ends the Transaction A.
4.1.6.1.4. Way of calling the method which is under transaction control¶
- Way of calling the method which is under transaction control
- Way of calling the method which is not under transaction control
Note
In order to bring internal method calls under transaction control
It is possible to enable transaction control for internal method calls as well by setting the AOP mode to “aspectj”. However, if internal method call of transaction management is enabled, the route of transaction management may become complicated; hence it is recommended to use the default “proxy” for AOP mode.
4.1.6.2. Settings for using transaction management¶
The settings required for using transaction management are explained.
4.1.6.2.1. PlatformTransactionManager settings¶
PlatformTransactionManager
.xxx-env.xml
Example of settings for managing the transaction using JDBC connection which is fetched from DataSource is given below.
<!-- (1) --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource" /> </bean>
S.No. Description (1) Specify the implementation class of PlatformTransactionManager.It is recommended to set id as “transactionManager”.
Note
When transaction management (Global transaction management) is required for multiple DBs (Multiple resources)
- It is necessary to use
org.springframework.transaction.jta.JtaTransactionManager
and manage transactions by using JTA functionality provided by application server.- When JTA is to be used in WebSphere, Oracle WebLogic Server and Oracle OC4J, a
JtaTransactionManager
which is extended for the application server is automatically set by specifying <tx:jta-transaction-manager/>.
¶ S.No. Class name Description
org.springframework.jdbc.datasource.DataSourceTransactionManager Implementation class for managing the transaction by calling API of JDBC(java.sql.Connection
).Use this class when Mybatis orJdbcTemplate
is to be used.
org.springframework.orm.jpa.JpaTransactionManager Implementation class for managing the transaction by calling API of JPA(javax.persistence.EntityTransaction
).Use this class when JPA is to be used.
org.springframework.transaction.jta.JtaTransactionManager Implementation class for managing the transaction by calling API of JTA(javax.transaction.UserTransaction
).Use this class to manage transaction with resources (Database/Messaging service/General-purpose EIS(Enterprise Information System) etc.) using JTS (Java Transaction Service) provided by application server.When it is necessary to execute the operations with multiple resources in a single transaction, it is necessary to use JTA for managing transactions.
4.1.6.2.2. Settings for enabling @Transactional¶
@Transactional
annotation is used.@Transactional
annotation are explained.xxx-domain.xml
<tx:annotation-driven /> <!-- (1) -->
S.No. Description (1)With use of <tx:annotation-driven> element in XML (bean definition file), transaction control gets enabled at the locations where @Transactional
annotation is used.
4.1.6.2.3. Regarding attributes of <tx:annotation-driven> element¶
Various attributes can be specified in <tx:annotation-driven> and default behavior can be customized.
xxx-domain.xml
<tx:annotation-driven transaction-manager="txManager" mode="aspectj" proxy-target-class="true" order="0" />
S.No. Attribute Description 1 transaction-manager Specify PlatformTransactionManager
bean. When omitted, bean registered with the name “transactionManager” is used.2 mode Specify AOP mode. When omitted, "proxy"
is the default value."aspectj"
can be also be specified. It is recommended to use"proxy"
.3 proxy-target-class Flag to specify whether proxy target is limited to class (this is valid only in case of mode=”proxy”). When omitted, it will be “false”.
- In case of false, if the target class has an interface, proxy is done using dynamic proxies functionality of standard JDK. When there is no interface, proxy is done using GCLIB functionality.
- In case of true, proxy is done using GCLIB function irrespective of whether interface is available or not.
4 order Order of Advice of AOP (Priority). When omitted, it will be “Last (Lowest priority)”.
4.1.7. Appendix¶
4.1.7.1. Regarding drawbacks of transaction management¶
4.1.7.2. Programmatic transaction management¶
In this guideline, “Declarative transaction management” is recommended. However, programmatic transaction management is also possible. Refer to Spring Reference Documentfor details.
4.1.7.3. Sample of implementation of interface and base classes to limit signature¶
- Interface to limit signature
// (1) public interface BLogic<I, O> { O execute(I input); }
S.No. Description (1) Interface to limit signature of implementation method of business logic.In the above example, it is defined as generic type of input (I) and output (O) information having one method (execute) for executing business logic.In this guideline, the above interface is called BLogic interface.
- Controller
// (2) @Inject XxxBLogic<XxxInput, XxxOutput> xxxBLogic; public String reserve(XxxForm form, RedirectAttributes redirectAttributes) { XxxInput input = new XxxInput(); // omitted // (3) XxxOutput output = xxxBlogic.execute(input); // omitted redirectAttributes.addFlashAttribute(output.getTourReservation()); return "redirect:/xxx?complete"; }
S.No. Description (2) Controller injects calling BLogic interface. (3) Controller calls execute method of BLogic interface and executes business logic.
To standardize process flow of business logic when a fixed common process is included in Service, base classes are created to limit signature of method.
- Base classes to limit signature
public abstract class AbstractBLogic<I, O> implements BLogic<I, O> { public O execute(I input){ try{ // omitted // (4) preExecute(input); // (5) O output = doExecute(input); // omitted return output; } finally { // omitted } } protected abstract void preExecute(I input); protected abstract O doExecute(I input); }
S.No. Description (4) Call the method to perform pre-processing before executing business logic from base classes.In the preExecute method, business rules are checked. (5) Call the method executing business logic from the base classes.
Sample of extending base classes to limit signature is shown below.
- BLogic class (Service)
public class XxxBLogic extends AbstractBLogic<XxxInput, XxxOutput> { // (6) protected void preExecute(XxxInput input) { // omitted Tour tour = tourRepository.findOne(input.getTourId()); Date reservationLimitDate = tour.reservationLimitDate(); if(input.getReservationDate().after(reservationLimitDate)){ throw new BusinessException(ResultMessages.error().add("e.xx.xx.0001")); } } // (7) protected XxxOutput doExecute(XxxInput input) { TourReservation tourReservation = new TourReservation(); // omitted tourReservationRepository.save(tourReservation); XxxOutput output = new XxxOutput(); output.setTourReservation(tourReservation); // omitted return output; } }
S.No. Description (6) Implement pre-process before executing business logic.Business rules are checked. (7) Implement business logic.Logic is implemented to satisfy business rules.