The primary purpose of COLA is to provide a simple set of “guidelines and constraints” for application architectures that can be replicated, understood, implemented, and controlled for complexity. In practice, the author found that COLA was still lacking in simplicity, so he made an “upgrade” for COLA. In this upgrade, he did not add any new functions, but deleted some concepts and functions as much as possible to make COLA more concise and effective.

Recently, my colleague told me that COLA, as an application architecture, has been selected as one of the application architecture options for Java application initialization of Ali Cloud.

This is really something. At This milestone, I began to look back and re-examine the gains and losses of COLA along the way.

COLA is undoubtedly successful as an architectural idea. But as a frame, I feel a little chicken ribs. Especially in the simplicity of the bad, feel done a lot of things gild the lily.

If you think about it, there are some features that I rarely use as a writer, and I can’t think of a reason why they exist.

With this in mind, I made an upgrade from COLA 2.0 to COLA 3.0. In this upgrade, I didn’t add any new features, but cut as many concepts and features as POSSIBLE. Making COLA more purely focused on application architecture rather than framework support and architectural constraints.

There was only one reason behind my decisions — Occam’s Razor.

Occam’s Razor

What Occam’s razor means is that Entities should not be multiplied unnecessarily. As Occam says in Proverbs 2:15, “Do not waste much on things, for with less you can do the same thing well.”

In the specific application process, we can follow the following principles to do things:

“If there are n theories for the same phenomenon, the simplest one is the correct one. If you can do something good with n, don’t do it with n+1.”

For example, does the emperor in the Emperor’s New Clothes have any clothes on at all? If you were there, chances are you were one of the ministers.

If you understand Occam’s Razor, you can use logic to determine who is the truth.

  • The first logic is as follows: suppose the emperor is really dressed → suppose stupid people cannot see → suppose you are stupid person → therefore you do not see the emperor dressed;

  • The second logic is as follows: suppose the emperor has no clothes → therefore you do not see the emperor wearing clothes.

The second explanation for the naked emperor is straightforward. The first explanation, perhaps because it is wrong, requires more assumptions to remedy the flaw, like a lie.

The truth needs no disguise. It is simple.

Another example is geocentric and heliocentric. Ptolemy’s geocentric model is an epicycle model. It allows people to calculate the motion of the planets quantitatively, and from that they can guess where they are.

In the late middle ages as incremental improvements were made in the observation instrument, people can more accurately measure the position and movement of the planet, the observed planets actual location with the computational result of this model, there is a deviation, can barely cope with at the beginning, later the little round increased to more than eighty, but still can not accurately calculate the position of the planets.

On his deathbed in 1543, Polish astronomer Copernicus published a historic work, The Theory of the Movements of the Heavenly Bodies. This system of theory proposed a clear idea: the sun is the center of the universe, and all the planets revolve around it. According to the theory, the Earth is one of the planets that rotates like a top and revolves around the sun like other planets.

Compared with the geocentric theory, which has been added to more than eighty circles, the Copernican calculation is in better agreement with the actual observation data. Therefore, the geocentric theory was eventually replaced by the heliocentric theory.

Twists and turns in design

Further investigation, our system, similar to the “geocentric theory” such a curved design, it is not a few.

From the perspective of system architecture, some twists and turns are caused by unreasonable division of system boundary, resulting in unclear responsibilities and confusion of dependence.

From an application architecture perspective, some of the twists and turns are due to over-design, inappropriate design in the pursuit of so-called flexibility and extensibility. As a result, the code logic that could have been presented intuitively is wrapped, hidden and forwarded…. This greatly hinders the readability and understandability of the code and increases the maintenance cost.

For example, I have seen countless business systems and love to talk about business process choreography. Therefore, in the business system, you can see all kinds of “zigzag design”.

For example, in a business system, I saw the following pipeline design. The essence of this design is to structurally break down a complex business operation into small processing units.

Unpacking is fine, but this approach is clearly not Occam enough (for more on structured decomposition, see my other article: How to Write Complex Business Code?). . As a maintainer, going into an “entry function” and having to look up the database to see which components were called is too convoluted, not intuitive, and not concise.

Same logic, according to the following way is not fragrant?

public class CreateCSPUExecutor {
    @Resource
    private InitContextStep initContextStep;
    @Resource
    private CheckRequiredParamStep checkRequiredParamStep;
    @Resource
    private CheckUnitStep checkUnitStep;
    @Resource
    private CheckExpiringDateStep checkExpiringDateStep;
    @Resource
    private CheckBarCodeStep checkBarCodeStep;
    @Resource
    private CheckBarCodeImgStep checkBarCodeImgStep;
    @Resource
    private CheckBrandCategoryStep checkBrandCategoryStep;
    @Resource
    private CheckProductDetailStep checkProductDetailStep;
    @Resource
    private CheckSpecImgStep checkSpecImgStep;
    @Resource
    private CreateCSPUStep createCSPUStep;
    @Resource
    private CreateCSPULogStep createCSPULogStep;
    @Resource
    private SendCSPUCreatedEventStep sendCSPUCreatedEventStep;
    public Long create(MyCspuSaveParam myCspuSaveParam){
        SaveCSPUContext context = initContextStep.initContext(myCspuSaveParam);
        checkRequiredParamStep.check(context);
        checkUnitStep.check(context);
        checkExpiringDateStep.check(context);
        checkBarCodeStep.check(context);
        checkBarCodeImgStep.check(context);
        checkBrandCategoryStep.check(context);
        checkProductDetailStep.check(context);
        checkSpecImgStep.check(context);
        createCSPUStep.create(context);
        createCSPULogStep.log(context);
        sendCSPUCreatedEventStep.sendEvent(context);
        returncontext.getCspu().getId(); }}Copy the code
public class CreateCSPUExecutor {
    @Resource
    private InitContextStep initContextStep;

    @Resource
    private CheckRequiredParamStep checkRequiredParamStep;

    @Resource
    private CheckUnitStep checkUnitStep;

    @Resource
    private CheckExpiringDateStep checkExpiringDateStep;

    @Resource
    private CheckBarCodeStep checkBarCodeStep;

    @Resource
    private CheckBarCodeImgStep checkBarCodeImgStep;

    @Resource
    private CheckBrandCategoryStep checkBrandCategoryStep;

    @Resource
    private CheckProductDetailStep checkProductDetailStep;

    @Resource
    private CheckSpecImgStep checkSpecImgStep;

    @Resource
    private CreateCSPUStep createCSPUStep;

    @Resource
    private CreateCSPULogStep createCSPULogStep;

    @Resource
    private SendCSPUCreatedEventStep sendCSPUCreatedEventStep;


    public Long create(MyCspuSaveParam myCspuSaveParam){
        SaveCSPUContext context = initContextStep.initContext(myCspuSaveParam);

        checkRequiredParamStep.check(context);

        checkUnitStep.check(context);

        checkExpiringDateStep.check(context);

        checkBarCodeStep.check(context);

        checkBarCodeImgStep.check(context);

        checkBrandCategoryStep.check(context);

        checkProductDetailStep.check(context);

        checkSpecImgStep.check(context);

        createCSPUStep.create(context);

        createCSPULogStep.log(context);

        sendCSPUCreatedEventStep.sendEvent(context);

        returncontext.getCspu().getId(); }}Copy the code

This writing method is simple, intuitive and easy to maintain. Compared with the previous method, it has the same component reuse. In the spirit of Occam’s Razor, by contrast, the front of the twisty design, although it looks a bit design, brings a bit of OCP benefit. But it increases the cost of understanding and cognition, and it’s not hard to tell which one is better.

COLA 3.0 upgrade

After such a long preparation, it is finally time to criticize the “zigzagging design” of COLA.

1. Remove the Command

In the initial phase of COLA, influenced by CQRS, the idea was to use command mode to handle user requests. The framework was designed to enforce constraints on Command and Query processing, and to force the logic of a Service to be split into CommandExecutor, preventing it from growing too fast.

Similar to the pipeline design introduced above, this design is a bit convoluted and not intuitive, as shown below:

public class MetricsServiceImpl implements MetricsServiceI{

    @Autowired
    private CommandBusI commandBus;

    @Override
    public Response addATAMetric(ATAMetricAddCmd cmd) {
        return commandBus.send(cmd);
    }

    @Override
    public Response addSharingMetric(SharingMetricAddCmd cmd) {
        return commandBus.send(cmd);
    }

    @Override
    public Response addPatentMetric(PatentMetricAddCmd cmd) {
        return  commandBus.send(cmd);
    }

    @Override
    public Response addPaperMetric(PaperMetricAddCmd cmd) {
        return  commandBus.send(cmd); }}Copy the code

It looks pretty clean, but it’s not intuitive which Executor handles the ATAMetricAddCmd. I also need to understand CommandBus and how CommandBus registers executors. It increases the cognitive cost, which is bad.

In that case, why not use Occam’s razor to get rid of the CommandBus. As shown below, the code is much more intuitive after removing CommandBus. The only loss is that we will lose the Interceptor functionality provided by the framework. However, Interceptor is where I want to go next.

public class MetricsServiceImpl implements MetricsServiceI{

    @Resource
    private ATAMetricAddCmdExe ataMetricAddCmdExe;
    @Resource
    private SharingMetricAddCmdExe sharingMetricAddCmdExe;
    @Resource
    private PatentMetricAddCmdExe patentMetricAddCmdExe;
    @Resource
    private PaperMetricAddCmdExe paperMetricAddCmdExe;

    @Override
    public Response addATAMetric(ATAMetricAddCmd cmd) {
        return ataMetricAddCmdExe.execute(cmd);
    }

    @Override
    public Response addSharingMetric(SharingMetricAddCmd cmd) {
        return sharingMetricAddCmdExe.execute(cmd);
    }

    @Override
    public Response addPatentMetric(PatentMetricAddCmd cmd) {
        return  patentMetricAddCmdExe.execute(cmd);
    }

    @Override
    public Response addPaperMetric(PaperMetricAddCmd cmd) {
        returnpaperMetricAddCmdExe.execute(cmd); }}Copy the code

2. Remove the Interceptor

When Interceptor was designed, it was based on CommandBus. In order to take advantage of the command mode, it added the Interceptor function. Its essence is an AOP process.

Given Spring’s AOP capabilities, this design is also a bit weak. As it turns out, few people use Interceptor when using the COLA framework, including myself. In that case, get rid of it.

3. Remove Convertor, Validator, Assembler

The importance of naming is unnecessary here. I was wondering if I could standardize the naming of some common functions from the framework level. But in practice, this idea is also found to be a little too idealistic.

I remember in the early days of the team’s practice of COLA, there were often arguments about what was Convertor and what was Assembler.

As I thought about it later, naming is important, but the scope is at best a team specification. It doesn’t really matter if you call your Validator a Validator or a Checker. Try to solve the problem of team engagement from the framework level, its effect is not very good, so we decisively remove the knife.

4. Class scan optimization

The idea of business identity and extension point is the core concept of TMF and the core methodology of multi-business support in Ali Business center.

COLA’s effort to provide a lightweight extension implementation saved the feature at Occam’s sword. COLA’s extension point design borrows from TMF, so in the previous design, its class scanning scheme is directly copied from TMF.

In fact, TMF’s scanning-like scheme is a bit redundant for COLA. COLA itself is built on Spring, which in turn is built on class scanning. Therefore, we can reuse Spring’s class scanning without having to write one ourselves.

In the Spring of the native, at least there are three ways to get to the user-defined Bean of the Annotation, the most concise is through ListableBeanFactory getBeansWithAnnotation method, Or use scanning ClassPathScanningCandidateComponentProvider package.

In this revision, I chose the getBeansWithAnnotation method, mainly to obtain the Bean of @Extension to realize the function of Extension point, and abandoned the original TMF class scanning implementation.

conclusion

The motivation for this upgrade was mainly because I did find some flashy features in the process of practicing COLA. As COLA is becoming more and more influential as the basic application architecture of Ali Cloud, IT is my responsibility to give you a correct guidance — eliminating the false and preserving the true, making it simple and effective, rather than introducing more complexity.

COLA actually consists of two parts:

On the one hand, COLA is an architectural idea, which is an application architecture integrating onion ring architecture, adapter architecture, DDD, clean architecture, TMF and other architectural ideas.

In this upgrade, the architectural thinking is largely unchanged, except that the Command concept has been removed so that CQRS is optional rather than mandatory.

COLA, on the other hand, is also a framework component. With this upgrade, I used Occam’s razor to cut most of the component capabilities, leaving only expansion points. The intention is not to use COLA as a framework to impose too many constraints on application developers, which is not in keeping with the simplicity and effectiveness of the style.

So, in summary, this is not so much an upgrade as a feature “downgrade,” a subtraction.

However, I believe that subtraction can make COLA more in line with the spirit of Occam and help COLA to go further with light weight.

COLA source address:Github.com/alibaba/COL…

Ali Cloud JAVA application scaffolding

Start.aliyun.com is an engineering scaffold generation platform based on the Spring-Initializr implementation. With just a few annotations and a few configurations, developers can quickly build distributed applications using more friendly Chinese and no network latency issues. The most important is to provide more localized component dependencies.

Click the link to experience ali Cloud JAVA application scaffolding immediately: start.aliyun.com/?utm_conten…

“Alibaba Cloud originator focuses on micro-service, Serverless, container, Service Mesh and other technical fields, focuses on the trend of cloud native popular technology, large-scale implementation of cloud native practice, and becomes the public account that most understands cloud native developers.”