Earlier in the Web Development Framework Derivation article we built a development framework step by step.

Under the circumstances, it was enough. However, with the gradual improvement of the project, the frequency of demand change gradually becomes higher than the frequency of new demand, and the disadvantages of the original framework become more and more obvious, so the framework needs to be upgraded and improved.

Let’s first look at the problems with the original framework and then, based on those problems, make improvements to the framework.

Problems with the original framework

  • Code generation issues
  • Parameter transfer problem
  • The Service layer problem
  • Test dependency issues
  • The Mapper. The problem of XML

Code generation issues

In the original framework, we based on various constraints, wrote a code generation components, by this component, we can according to the selected table to generate the Controller, the Service, the Model, Mapper and so on a series of classes, that is to say, as long as finish table, can be directly generated a set of CRUD, You can start and test it. This may look great at the beginning of the project, but there are many limitations when requirements change.

First, the generated code logic is solidified. If you make a few tweaks, you need to adjust the component that generated the code, repackage it, upload it to the JAR repository, modify the component version, and then generate the code. The whole process is too cumbersome.

Secondly, many compromises were made to facilitate code generation:

  • To make it easier to regenerate table fields after they are modified, many classes abstract a base class for manipulating Model fields. These base classes cannot be manually modified because they are overridden with each build. This actually leads to an increase in the number of classes.
  • The resulting CRUD is solidified and cannot be manually adjusted. If the generated CRUD does not meet the requirements, it cannot be modified directly in the code. You can only copy one copy for modification because it will be overwritten if you build it again. This leads to redundant code.
  • Param and Result delegate to the Model so that when the Model changes, the corresponding field changes are known at compile time. But it also introduces a number of problems, which we discuss separately in the section “Parameter passing problems.”

Parameter transfer problem

In order to facilitate code generation, it was decided that both Param and Result should inherit Model, which resulted in the following problems:

  • Makes both Param and Result dependent on Model. However, Param and Result are view layer models, while Model is persistence layer Model, so the degree of evolution of the two is not consistent. However, the inheritance relationship now causes the view layer model to evolve in sync with the persistence layer by default. Of course, you can adjust Param and Result manually, but this removes the advantage of code generation.
  • Param and Result delegate fields, that is, they have no fields, and use getters and setters to set values into the Model. This makes it impossible to use Lombok to simplify getters and setters, resulting in more lines of Param and Result code
  • At the same time, for Swagger, some annotations need to be field based, so some functionality cannot be implemented (for example, ModelAttribute) and can only be handled by additional means (for example, field documents need to be implemented through ApiImplicitParams).
  • Cruds are all based on the same Param and Result, which results in many useless fields displayed on the front-end interface, making it more difficult for the front-end to understand the interface

The Service layer problem

The Service layer has the following problems:

  • The Service layer is overloaded with responsibilities, including transaction processing, parameter setting, and business logic

  • The resulting code in the Service is spaghetti code, which is not conducive to understanding the business logic

  • While transaction annotations are added directly to the class, Spring’s default transaction mechanism causes logical calls like the following to not throw the expected exception

    // PostService public String savePost(Post post) { postRepository.save(post); for(PostDiscuss discuss : Post.getdiscuss ()) {// The RuntimeException cannot be captured, but the exception discussservice.save (discuss) is specified for TransactionRollBack; DiscussService public String savePost(PostDiscuss discuss) {throw new RuntimeException(” save failed “); // discussService public String savePost(PostDiscuss discuss) {throw new RuntimeException(” save failed “); }

Test dependency issues

The core business logic in the Service is still dependent on Spring for testing, and as the project gets bigger and bigger, it takes longer and longer to start the project, perhaps a minute or more. This makes unit testing less and less efficient.

The Mapper. The problem of XML

In interviews, I often ask the following questions:

  • What are interfaces for in Java?
  • Why do services and DAOs write interfaces and then implement them?
  • The interface and implementation are in the same module and will have to be repackaged anyway. Isn’t writing an extra interface just a few more lines of code?
  • Mybatis (Mybatis, Mybatis, Mybatis, Mybatis, Mybatis, Mybatis, Mybatis, Mybatis, Mybatis, Mybatis, Mybatis, Mybatis, Mybatis, Mybatis, Mybatis, Mybatis, Mybatis, Mybatis Mybatis can’t load mapper. XML dynamically, so what are the advantages of using SQL as an independent XML file?

My answer to the last question is, for most projects, no! How easy a project is to deploy and scale depends not on the framework you use, but on your design.

In the case of mapper. XML, Mybatis separates SQL from code, but if you put mapper. XML and code in the same module in your project, this advantage is lost. Without this advantage, do we need to write mapper.xml separately? My option is not to write and use the annotations provided by Mybatis.

At the same time, in order to solve the Service layer’s strong dependence on the DAO layer (in this case, Mybatis), some improvements are made to the framework, decoupling the Service and DAO layer. See the improvement plan below for details.

Framework improvement scheme

To address these issues, the framework has been adjusted as follows:

  • Separate Param, Result, and Model
  • Replacement code generation
  • Independent business logic
  • Model layer optimization

Separate Param, Result, and Model

As mentioned above, strong coupling of Param, Result, and Model can cause many problems, so here we separate Param, Result, and Model. Each is an independent Bean, which solves the above problems. But two new problems were introduced:

  • First, obviously, it increases the amount of manual coding. When a table changes fields, three or more classes need to be changed
  • Secondly, the code between data transfer is added. That is, when Param is passed to Model, it needs to assign values to the fields. If you set field by field, it will add a lot of boring code. Using reflection has some impact on performance

So how do you solve these two problems? First of all, it’s definitely impossible to masturbate. Some means of automation need to be provided.

For assignment, Spring provides BeanUtils to simplify the process, and although values are set based on reflection, this performance loss is not significant at this stage. However, BeanUtils cannot be copied for different types of attributes. Let’s say I have a Domain object Book, which has a field Author in it, and NOW I want to assign to BookResult, which has a field AuthorResult, so BeanUtils cannot be assigned. So I wrote a Gson-based utility class to handle it, and a property copy of BeanUtils for 10,000 performance tests took over 500 milliseconds, while a Gson-based utility class took around 300 milliseconds.

For table field generation, the IDE provides a script by default to generate POJOs from tables if IDEA is used. We can use this script to generate the Model and then copy the fields to Param and Result to simplify field writing. I modified the script to fit the project requirements. The major additions to Lombok include new class and field annotations.

Replacement code generation

In response to the problem with the code generation component above, I adjusted the way the code is generated. Instead of being generated based on components, it’s generated based on the FileTemplate, LiveTemplate, and Scripted Extensions of the IDEA itself. It is not possible to generate multiple files at once in this way, but since the generation logic is basically one-time, the impact is not significant. Code generation components are more efficient than a combination of FileTemplate, LiveTemplate, and Scripted Extensions for the first time, but with flexibility for later adjustments, Obviously a combination of FileTemplate, LiveTemplate, and Scripted Extensions is better than a code-generated component:

  • First, when the file structure is changed, you only need to modify the FileTemplate and export the configuration file to the project team members.
  • Similarly, when the LiveTemplate is modified, you only need to modify the corresponding LiveTemplate and export the configuration file to the project team members.
  • Second, if you want to generate any file, you only need to generate it for that file
  • Third, after a complete file is generated through FileTemplate, it can be quickly modularized through LiveTemplate
  • Finally, FileTemplate can be set at the project level, meaning that each project can have a separate FileTemplate

The specific operation process is demonstrated below.

Independent business logic

To solve the problem of Service and test, split the original three layers of Controller, Service and Model into four layers:

  • The Controller is responsible for receiving and returning front-end data, as well as unified exception handling
  • The Service is responsible for the transaction and the assembly of the Domain layer logic. There would be no transaction nesting problem, which would cause the problem of not catching the expected exception
  • Domain is responsible for business logic
  • Model is responsible for data persistence

This relieves the responsibility of the Service and eliminates the problem of transaction nesting.

Model layer optimization

As mentioned above, mapper.xml was eventually abandoned in the framework in favor of Mybatis annotations for persistence. The use of annotations avoids the writing of XML code, but does not solve the framework’s strong dependence on Mybatis. Therefore, the Repository interface layer is added in the Domain, which is used to define the persistence operation of the Domain, and the Repository is implemented in the Model layer, which is the Mybatis implementation. This has two advantages:

  • Dependency inversion: The Domain is dependent on the Model layer, and now the Model layer is dependent on the Domain layer, so that when I want to replace Mybatis, Domain is completely unaware.
  • Independent testing: Because domains are not dependent on any other layer now, they can be tested independently of the database and container. So that the efficiency of testing does not become less and less with the development of the project

Framework Improvement details

Now that you know how to improve the framework, let’s get started. The main change was to the way the code was generated, that is, to write FileTemplate, LiveTemplate, and ScriptedExtensions. For a brief description of the three functions, begin with ScriptedExtensions.

Scripted Extensions

First, explain what a Scripted Extension is. As we all know, today’s ides are plug-in, which means that we can develop plug-ins that extend existing IDE functionality through the plug-in development kit provided by the developer. But writing a plug-in requires a specific development environment, and if it is a simple function, you have to struggle with the development environment, which can be quite troublesome. So IDEA provides Scripted Extensions, which can be translated as a simplified version of a plug-in that extends IDE functionality with scripts.

IDEA provides the Database function to connect to the Database for related operations. When you connect to the database, right click on the table, and you’ll see the Scripted Extensions option, which includes a Groovy script that generates POJOs based on the table.

But the function is relatively low:

  • The package name is written out: com.sample
  • No table annotation was generated
  • There is no Lombok based simplification of getters and setters

Luckily, you can modify it yourself based on the script, as you just clicked on the Go to Scripts Directory option in the Scripted Extensions menu.

Ctrl+ C, CtrL-v, copy the Groovy file, rename it, and modify it based on the script. Specific how to modify, according to their own needs, which is mainly according to the table information of String splicing.

FileTemplate

FileTemplate is a template for generating files provided by IDEA. You can click on File->New… In the future, all kinds of files are implemented based on FileTemplate. We can create custom classes like Controller, Service, and Domain using FileTemplate to simplify creation.

To use this method, press Ctrl-Alt-s to call out the Settings menu. Then go to Editor->File And Code Template And add Template.

A few notes:

  • The following description lists some of the default parameters and their functions
  • You can also customize variables, which will be prompted when created if they are not assigned a value
  • Templates are Based on Velocity, so if you are familiar with Velocity, you should be able to get started
  • The Enable LiveTemplate option enables the LiveTemplate variable on FileTemplate, using the #[[]]# wrap. However, for Java creation, this feature is buggy and does not locate the desired location, so it is not used for now

Once created, you can see the template in the New menu.

LiveTemplate

The LiveTemplate is actually CodeSnippet. The creation method is similar to that of FileTemplate. Press Ctrl-Alt-s to call out the Settings menu and go to Editor->Live Template to add a new Template.

A few notes:

  • The variable here is wrapped with $$
  • Each variable is a placeholder that you can manually enter after TAB expansion
  • Edit Variables, in the lower right corner, is used to assign values to variables. IDEA provides methods and can also set default values
  • In the change link below, you can select where LiveTemplate will work, such as only at the Java class declaration

Encoding process

After creating the above templates, the coding process is as follows:

  • Right click on the table to generate the Model through the Scripted Extensions
  • Use FileTemplate to quickly generate Controller, Service, Domain, and other classes
  • Use LiveTemplate to write code quickly

conclusion

In this paper, through sorting out and solving the problems of the original framework, the framework is upgraded to adapt to the development and promotion of the project.