How to write Java business code? This is also a lot of norms

Posted by rurouni on Thu, 10 Mar 2022 02:05:36 +0100

Why write business code?

Share a painful project maintenance experience directly to see if you have similar experience. At that time, I took over a maintenance project and received the task of adding a new display field as soon as I went to work. I thought it should be a small demand that can be solved in minutes, but I didn't expect it to start my painful journey. After combing the associated APIs, I found that each api is from the controller control layer - "service" - service layer - dao data layer, and even each api corresponds to an sql query.

However, there is a lot of similar code between all APIs. When I started reading the code, I found a special controller, which includes identity verification, parameter verification, various business codes, various if else, for loop statements, and even dao layer logic.

What's more sad is that the project has no documents, the code has almost no comments, and there are no test cases. I still comb the business directly through the code. Many attribute fields can't understand what they represent, for example, ajAmount and gjjmount; Write status in (1, 2, 4, 6), case when, and many other magic number conditional judgments in the sql statement.

Finally, I directly captured the package and called the api. Then, by matching with the fields on the display side of the page, I know what ajAmount and gjjmount mean respectively in some fields of mortgage loan, provident fund code and status. Do you have similar project maintenance experience?

In my opinion, as long as we reject chimney development in api, reject All in one in business code, and make code comments for the project, we can write code that is easy to read and easy to expand.

How api rejects chimney development

The above api development process is a typical chimney development mode. All api services are similar to similar businesses, but each api is completely developed independently. Its development process is shown in the figure below:

The above development process has several disadvantages, as follows:

The business code is repeated. In different service implementations, if the business is similar, there will be a large number of duplicate codes.

The change of database table structure needs to modify all dao layers involved, and the maintenance cost is relatively high.

For such similar services, the api layer defines their own display objects, the dao layer is responsible for obtaining the full amount of data (for example, if the user queries, it obtains the data of the whole user table field), and the service layer defines the business objects. According to the judgment of different business types of different APIs, the business objects are transformed according to the data groups queried by dao, and the transformation from the business objects to the api display objects.

The development process is shown in the figure below:

This development model has the following advantages:

The business code is concentrated in the service layer, focusing on the encapsulation of the business object bo and the transformation of the business object to the class display layer vo; Encapsulating reuse logic can greatly reduce duplicate code. If the design pattern is easy to expand from the beginning, the later maintenance will be much faster.

Database changes only involve the db layer, which can respond quickly in various businesses.

How does the business code reject All in one?

The most prominent disadvantage of the above controller code is that the code can not be reused at all, and the characteristics of object-oriented encapsulation, integration and polymorphism are not used at all. In business development, it is generally permission verification, parameter verification, business judgment, business object conversion and database operation.

My approach is to abstract the business, extract the public code and call it in the form of configuration, so that the business code can select the specified permission verification and parameter verification in a pluggable way. In short, it is to make good use of the idea of AOP aspect oriented programming. Examples are as follows:

Permission verification:

The permission of aop can be checked by the logic controller. When filtering the user's data, use the interceptor of the controller to obtain various permissions owned by the user, save the user data in the context threadload, and intercept the specified url through configuration. In the business layer, the user authority data is obtained from the context to filter various data services, and the designated calls of various interception services are realized through aop.

Parameter verification:

Use Java validation to extend common fields, such as phone number and ID card. For details, please refer to how to use validation to verify parameters?, Other similar checks are reused in the project.

Business judgment: use design patterns to encapsulate, integrate and expand different types of business development; In this way, in the later expansion, you can expand subclasses for new businesses based on the development closure principle.

Number of business object conversions:

In the process of business development, according to the requirements of Alibaba's R & D specifications, there are do (objects with consistent database table structure), BO (business objects), dto (data transmission objects), VO (display layer objects) and Query (Query objects).

Using MapStruct, you can flexibly control the conversion specification between different attribute values, which is better than org springframework. beans. BeanUtils. The copyproperties () method is more flexible.

Refer to this article:

https://www.javastack.cn/arti...

Example:

public interface CategoryConverter {

    CategoryConverter INSTANCE = Mappers.getMapper(CategoryConverter.class);
     
    @Mappings({
            @Mapping(target = "ext", expression = "java(getCategoryExt(updateCategoryDto.getStyle(),updateCategoryDto.getGoodsPageSize()))")})
    Category update2Category(UpdateCategoryDto updateCategoryDto);
     
    @Mappings({
            @Mapping(target = "ext", expression = "java(getCategoryExt(addCategoryDto.getStyle(),addCategoryDto.getGoodsPageSize()))")})
    Category add2Category(AddCategoryDto addCategoryDto);
}

DB database public field filling:

For example, public fields, generation date, creator, modification time, and modifier are encapsulated in the form of plug-ins. MetaObjectHandler is used in mybatis plus to complete the filling of unified field values before sql execution.

Business platform field query and filtering:

In the development of the middle platform, the code columns of different platforms are used to isolate the business data of different platforms. For the implementation of multi tenant filtering mechanism based on mybatis plug-in mechanism, please refer to how to use mybatis plug-in to realize multi tenant data filtering?.

Add custom filter conditions to dao layer methods or interfaces. Examples are as follows:

@Mapper
@Repository
@MultiTenancy(multiTenancyQueryValueFactory = CustomerQueryValueFactory.class)
public interface ProductDao extends BaseMapper<Product> {

}

Cache usage:

In Spring development, spring cache is usually integrated and used in the form of annotations. Integrate redis and customize the default time settings. You can refer to (Spring Cache+redis custom cache expiration time).

Examples are as follows:

/**
* Use the CacheEvict annotation to update the cache of the specified key
*/
@Override
@CacheEvict(value = {ALL_PRODUCT_KEY,ONLINE_PRODUCT_KEY}, allEntries = true)
public Boolean add(ProductAddDto dto) {

//   TODO add product update cache
}

@Override
@Cacheable(value = {ALL_PRODUCT_KEY})
public List<ProductVo> findAllProductVo() {
      
    return this.baseMapper.selectList(null);
}

@Override
@Cacheable(value = {ONLINE_PRODUCT_KEY})
public ProductVo getOnlineProductVo() {
      
     //   TODO set query criteria
    return this.baseMapper.selectList(query);
}

How to make code comments for the project?

Use of enumeration classes:

In the business, especially the state value, add the annotation of the state enumeration value to the vo object of the external api, and use the @ link annotation to connect directly to the enumeration class, so that the developer can see it at a glance.

Examples are as follows:

public class ProductVo implements Serializable {   /**
     * Audit status
     * {@link ProductStatus}
     */
    @ApiModelProperty("state")
    private Integer status;
}

Migrate sql query criteria:

Avoid writing fixed general filter conditions in the sql layer and migrate to the service layer for processing.

Examples are as follows:

// sql query criteria

SELECT * from product
where status != -1 and shop_status != 6


// Set conditions for various status values in the business layer
public PageData<ProductVo> findCustPage(Query query ){

       // Product online, display status
       query.setStatus(ProductStatus.ONSHELF);
       // Product display status
       query.setHideState(HideState.VISIBAL);
       // Store not offline
       query.setNotStatus(ShopStatus.OFFLINE);
    return   productService.findProductVoPage(query);
}   

Specification of bonus items

Use of optimistic lock and pessimistic lock

Optimistic lock (using Spring AOP + annotation to implement java optimistic lock based on CAS) sets the number of retries and retry time. Optimistic lock is used in simple object attribute modification. An example is as follows:

@Transactional(rollbackFor = Exception.class)
@OptimisticRetry
public void updateGoods(GoodsUpdateDto dto) {

    Goods existGoods = this.getGoods(dto.getCode());

    // Attribute logic judgment//

    if (0 == goodsDao.updateGoods(existGoods, dto)) {

        throw new OptimisticLockingFailureException("update goods optimistic locking failure!");
    }
}

Pessimistic locks are used when business scenarios are complex and there are many associations. For example, when modifying the SKU attribute, you need to modify the price, inventory, classification and other attributes of goods. At this time, you can lock the aggregation root product of the association relationship. The code is as follows:

@Transactional
public void updateProduct(Long id,ProductUpdateDto dto){

    Product existingProduct;
    // Lock the data according to the product id
    Assert.notNull(existingProduct = lockProduct(id), "Invalid product id!");


    
    // TODO logical condition judgment 
     
    // TODO modify commodity attribute, name and status
         
    // TODO modify price
     
    // TODO modify inventory
     
    // TODO modify commodity specifications
}

Use of read write separation

In development, mybatisplus is often used to realize the separation of reading and writing. The conventional query operation is to query from the database. The query request can be made without database transactions, such as list query. An example is as follows:

    @Override
    @DS("slave_1")
    public List<Product> findList(ProductQuery query) {
     
        QueryWrapper<Product> queryWrapper = this.buildQueryWrapper(query);
        return this.baseMapper.selectList(queryWrapper);
    }

mybatisplus dynamic data source is the main database by default. In order to ensure the continuity of data, transaction control needs to be added to write operations. Simple operations can be directly annotated with @ Transactional annotation. If the write operation involves unnecessary queries, or uses third-party plug-ins such as message middleware and reids, declarative transactions can be used to avoid the problem of long transactions in the database caused by abnormal queries or third-party queries.

For example, when the product goes offline, use reids to generate the log code. After the product related write operation is completed, send a message. The code is as follows:

public void offlineProduct(OfflineProductDto dto){

    // TODO modification is the query operation involved

    // TODO uses redis to generate business code

    // Use declarative transactions to control database operations related to product state modification
    boolean status = transactionTemplate.execute(new TransactionCallback<Boolean>() {
        @Nullable
        @Override
        public Boolean doInTransaction(TransactionStatus status) {
              try {

                 // TODO change product status

              } catch (Exception e) {
                 status.setRollbackOnly();
                 throw e;
              }
              return true;
           }
        });

    // TODO message sending Middleware

}

Automatic disaster recovery of database

Combined with the configuration center, the automatic disaster recovery of the database is simply realized. Taking nacous configuration center as an example, how to use Nacos to realize automatic switching of database connection?. Add the @ EnableNacosDynamicDataSource configuration annotation to the springboot startup class to realize the dynamic switching of database connection without intrusion. An example is as follows:

Recommend a basic Spring Boot tutorial and practical example: https://github.com/javastacks...

@EnableNacosDynamicDataSource
public class ProductApplication {

    public static void main(String[] args) {
        SpringApplication.run(ProductApplication.class, args);
    }

}

Preparation of test cases

Based on the principle of TDD, combined with junit and mockito to realize the test cases of service functions, why write unit tests? How to write unit test based on junit?. When adding or modifying an object, you need to verify the validity of the input parameters and various properties of the object after the operation. For example, after adding the api category successfully, the default status of the added api category and other parameters are as follows:

// Add test cases for categories
@Test
@Transactional
@Rollback
public void success2addCategory() throws Exception {

    AddCategoryDto addCategoryDto = new AddCategoryDto();
    addCategoryDto.setName("clothing");
    addCategoryDto.setLevel(1);
    addCategoryDto.setSort(1);
    Response<CategorySuccessVo> responseCategorySuccessVo = this.addCategory(addCategoryDto);
    CategorySuccessVo addParentCategorySuccessVo = responseCategorySuccessVo.getData();
    org.junit.Assert.assertNotNull(addParentCategorySuccessVo);
    org.junit.Assert.assertNotNull(addParentCategorySuccessVo.getId());
    org.junit.Assert.assertEquals(addParentCategorySuccessVo.getPid(), ROOT_PID);
    org.junit.Assert.assertEquals(addParentCategorySuccessVo.getStatus(), CategoryEnum.CATEGORY_STATUS_DOWN.getValue());
    org.junit.Assert.assertEquals(addParentCategorySuccessVo.getName(), addCategoryDto.getName());
    org.junit.Assert.assertEquals(addParentCategorySuccessVo.getLevel(), addCategoryDto.getLevel());
    org.junit.Assert.assertEquals(addParentCategorySuccessVo.getSort(), addCategoryDto.getSort());
}

// After adding a new category successfully, return to query CategorySuccessVo by id
public CategorySuccessVo add(AddCategoryDto addCategoryDto, UserContext userContext) {

    Category addingCategory = CategoryConverter.INSTANCE.add2Category(addCategoryDto);
    addingCategory.setStatus(CategoryEnum.CATEGORY_STATUS_DOWN.getValue());
    if (Objects.isNull(addCategoryDto.getLevel())) {
        addingCategory.setLevel(1);
    }
    if (Objects.isNull(addCategoryDto.getSort())) {
        addingCategory.setSort(100);
    }
    categoryDao.insert(addingCategory);
    return getCategorySuccessVo(addingCategory.getId());
}
You also need to verify the parameters of adding categories. For example, the verification that the name cannot be duplicate is as follows:

// Add entry parameter of category
public class AddCategoryDto implements Serializable {

private static final long serialVersionUID = -4752897765723264858L;

// The name cannot be empty or duplicate
@NotEmpty(message = CATEGORY_NAME_IS_EMPTY, groups = {ValidateGroup.First.class})
@EffectiveValue(shouldBeNull = true, message = CATEGORY_NAME_IS_DUPLICATE, serviceBean = NameOfCategoryForAddValidator.class, groups = {ValidateGroup.Second.class})
@ApiModelProperty(value = "Category name", required = true)
private String name;

@ApiModelProperty(value = "Category level")
private Integer level;

@ApiModelProperty(value = "sort")
private Integer sort;

}

//Add failed verification test cases
@Test
public void fail2addCategory() throws Exception {

    AddCategoryDto addCategoryDto = new AddCategoryDto();
    addCategoryDto.setName("clothing");
    addCategoryDto.setLevel(1);
    addCategoryDto.setSort(1);

    // Name is empty
    addCategoryDto.setName(null);
    Response<CategorySuccessVo> errorResponse = this.addCategory(addCategoryDto);
    org.junit.Assert.assertNotNull(errorResponse);
    org.junit.Assert.assertNotNull(errorResponse.getMsg(), CATEGORY_NAME_IS_EMPTY);
    addCategoryDto.setName("clothing");

    // Category added successfully
    this.addCategory(addCategoryDto);
     // Duplicate name
    errorResponse = this.addCategory(addCategoryDto);
    org.junit.Assert.assertNotNull(errorResponse);
    org.junit.Assert.assertNotNull(errorResponse.getMsg(), CATEGORY_NAME_IS_DUPLICATE);

}

Original link: https://blog.csdn.net/new_com...

Copyright notice: This article is the original article of CSDN blogger "ilovoverfly", which follows the CC 4.0 BY-SA copyright agreement. Please attach the original source link and this notice for reprint.

Recent hot article recommendations:

1.1000 + Java interview questions and answers (2022 latest version)

2.Hot! The Java collaboration is coming...

3.Spring Boot 2.x tutorial, too complete!

4.Don't write the explosive category full of screen, try the decorator mode, this is the elegant way!!

5.Java development manual (Songshan version) is the latest release. Download it quickly!

Feel good, don't forget to like + forward!

Topics: Java