SOFA (Scalable Open Financial Architecture)

It is a finance-level distributed middleware independently researched and developed by Ant Financial, which contains all components required for the construction of the finance-level cloud native architecture. It is the best practice forged in the financial scene.


SOFABoot is an open source development framework based on Spring Boot by ant Financial middleware team. SOFABoot supports modular development capability based on Spring context isolation from version 2.4.0. SOFABoot modules include Java code, Spring configuration files are also included, and each SOFABoot module is a separate Spring context.


The Github address for SOFABoot is:

https://github.com/alipay/sofa-boot

The pitfalls of traditional modularity

Before introducing SOFABoot modularization, let’s review the disadvantages of traditional modularization again. This part of the content refers to ant Financial’s business system modularization —- modular isolation scheme published by Lu Zhi (SOFA open source leader).


In a simple Spring/SpringBoot system, it is common to see modules in a system layered in the following way, as shown in the left part of the figure below, a system is simply divided into Web layer, Service layer, and DAL layer.



As the system carries more business, the system may evolve in the way shown on the right. On the right side of the diagram, there is a system that hosts two services: “Cashier” and “Pay”. There may be some dependency between the two services, and “Cashier” needs to call on the ability of Pay to make the payments.

But in this modular scenario, the Spring context is still the same, and classes are not segregated, which means that any Bean in the Pay Service module can be dependent on the Cashier Service module. In extreme cases, the following may occur:



The Cashier Service mistakenly calls an internal Bean in the Pay Service, causing a tight coupling between the two modules.


The problem with this traditional modularity is that it is not completely modularized. Although in the research and development, by dividing modules, put the specific responsibility of the class into a specific module, to achieve the “physical location” of the class cohesion. However, at runtime, as there is no means of isolation, as a module developer, there is no way to clearly know what the external interface is provided by the other module, which beans I can directly inject to use, which beans are your internal beans, I can not use. In the long run, the coupling between modules will become more and more serious, and the division of the original module becomes meaningless. When the system gets bigger and bigger, and finally needs to do a servitization split, it needs to spend a lot of energy to sort out the relationship between modules.

Introduction to SOFABoot modularity

In order to solve the problem that traditional modularity solutions are not completely modularized, SOFABoot supports modularity based on Spring context isolation since version 2.4.0. Each SOFABoot module uses a separate Spring context, and each module is self-contained. Modules communicate with each other via JVM services to avoid tight coupling between modules:

As you can see from the system architecture diagram above, SOFABoot modularity consists of three basic concepts, namely SOFABoot module, JVM Service and Root Application Context:

  • SOFABoot module: A SOFABoot module is a common Jar package containing Java code, Spring configuration files, SOFABoot module identikit, and more. Each SOFABoot module is a separate Spring context.

  • JVM Services: After context isolation, beans between modules cannot be directly injected. JVM Services are used for inter-module communication and for publishing and referencing module services.

  • Root Application Context: The Spring Context generated after a SOFABoot Application calls the SpringApplication.run(args) method, which is the Parent of all SOFABoot modules.


The rest of this article introduces the basic concepts of SOFABoot module lookup and refresh, JVM Service and component management, and Root Application Context, respectively. After that, the require-module, spring-parent and parallel startup of SOFABoot Module will be briefly introduced, and the practical suggestions for modular development will be given at the end.

SOFABoot module lookup and refresh

The sequence diagram of SOFABoot module search and refresh is as follows:



In SOFABoot, we defined the SofaModuleContextRefreshedListener, the class will monitor the Root Application Context send ContextRefreshedEvent events, The response to this event was chosen for two reasons:

  1. ContextRefreshedEvent is a standard Spring event that is relatively generic.

  2. SOFABoot module refreshes need to wait until Root Application Context refreshes, because SOFABoot modules may depend on bean definitions in Root Application Context.


SofaModuleContextRefreshedListener after listening to ContextRefreshedEvent events will create PipelineContext object, Add in PipelineContext ModelCreatingStage, SpringContextInstallStage, ModuleLogOutputStage three stages, the role of the three stages respectively as follows:

  • ModelCreatingStage: Finds all valid SOFABoot modules in the current ClassPath

  • SpringContextInstallStage: for each SOFABoot module to find a new Spring Context, loading SOFABoot module in the Spring configuration file;

  • ModuleLogOutputStage: Outputs all SOFABoot modules that have been refreshed successfully and failed to be refreshed, facilitating users to quickly locate problems.


A call to the PipelineContext process method triggers the execution of the three stages in sequence, flushing all SOFABoot modules contained in the current ClassPath.

JVM Services and component management

With context isolation, beans between modules cannot be directly injected, and JVM Services are used for inter-module communication, publishing and referencing module services.

To implement service discovery across modules, we define a component management interface, ComponentManager, within SOFABoot:

public interface ComponentManager {
    /**
     * register component inthis manager * * @param componentInfo component that should be registered */ void register(ComponentInfo componentInfo);  /** * get concrete component by component name * * @param name component name * @return concrete component
     */
    ComponentInfo getComponentInfo(ComponentName name);
}Copy the code

ComponentInfo is an interface, used to express component information, currently contains two concrete implementation, respectively is ServiceComponent and ReferenceComponent, respectively represents services and references. ComponentName is a unique identifier for ComponentInfo, used to distinguish different ComponentInfo implementations.

ComponentManager implementation class contains a ComponentName as key, ComponentInfo as value Map, used to store all registered ComponentInfo:

public class ComponentManagerImpl implements ComponentManager {
    /** container for all components */
    protected ConcurrentMap<ComponentName, ComponentInfo> registry;
    
    // other definition

}Copy the code

When publishing a JVM service, we call the Register method to register the JVM service with ComponentManager, and when a service call occurs, we call the getComponentInfo method, Find and invoke services published by other modules in ComponentManager.

Root Application Context

SOFABoot extends from Spring Boot. SOFABoot applications are launched using the SpringApplication.run(args) method, which generates a Spring context after the application is called. We’ll call it the Root Application Context. The Root Application Context is a special presence in SOFABoot modularity. It is the parent of each SOFABoot module Context and is designed to be available out of the box: Each time a SOFABoot Application adds a Starter definition, the Starter might define some beans that are only valid in the Root Application Context by default. By defining the Root Application Context as the Parent of each SOFABoot module Context, each SOFABoot module can find these Starter Bean definitions out of the box.


In addition to defining some beans, the Starter may also define BeanPostProcessor and BeanFactoryPostProcessor. For these two special Bean definitions, the subcontext light can detect that the Bean definition is insufficient. The definition of these two types of beans must be copied to the current context to take effect, so when we refresh the SOFABoot module’s corresponding context, All beanPostProcessors and BeanFactoryPostProcessors defined in the Root Application Context are copied into the Context of the SOFABoot module, So that some of the Starter defined processors can be used directly in the SOFABoot module context, For example the runtime – sofa – the boot – will define ServiceAnnotationBeanPostProcessor starter, this class is mainly used to implement annotations publishing service, automatically after the copy, Publishing services based on annotations in SOFABoot modules is supported as long as the Run-time SOFA-boot-starter dependency is added.

Require-module, spring-parent, and parallel startup modules

When refreshing the SOFABoot module, it is possible that module A published A JVM Service that needs to be called in the init method of A Bean in module B, assuming that module B started before module A, The Bean of module B will fail init because the JVM Service of module A has not been published, causing the Spring context to fail to start. In this case, we can specify require-module in SOFA -module.properties to force module A to start before module B.


In SOFABoot applications, each SOFABoot module is a separate Spring context, and these Spring contexts are isolated from each other. While there are many benefits to this modularity approach, there may be some inconvenience in some scenarios where you can use spring-parent to get through the Spring context of both SOFABoot modules. For example, the DAL module can be the Parent of the Service module, so that the Service module can use the DataSource definition defined by the DAL module directly, without having to publish a DataSource as a JVM Service.


SOFABoot calculates Module dependency trees based on require-Module and spring-parent. For example, modules B and C depend on Module A, Module E depends on Module D, and Module F depends on Module E:

The dependency tree will ensure that the module is A must in the module B and C modules before start, before the modules in the module D E, before the modules in the module F E, but not rely on tree definition module and module B and C modules B, C and D, E, F, the startup sequence between the start serial between several modules, also can start in parallel. SOFABoot starts modules in parallel by default, which can greatly speed up application launches.

Practical advice

Each SOFABoot module uses a separate Spring context, and modules communicate with each other via JVM Services. When publishing services, we recommend publishing services in a Service dimension, similar to the use of RPC, providing a Facade package containing interface definitions, The module then implements the interface and publishes the service.


Some implementations are not appropriate to be defined in SOFABoot modules, such as the Controller definition, which is the presentation layer implementation, and SOFABoot modules, which are in the business layer. We do not recommend or support defining Controller components in SOFABoot modules. The Controller component definition is recommended to be placed in the Root Application Context. If the Controller component needs to call a service published by the SOFABoot module, it can be referenced directly with annotations. Concrete examples we can see real fuck | SOFABoot based on modular development of example.


Some modules are not suitable to be defined as SOFABoot modules, such as the Util module, which defines utility classes, and the Facade module, which defines service interfaces and does not involve the publication and reference of services. It is recommended not to define these modules as SOFABoot modules.

conclusion

This paper mainly introduces the realization principle of SOFABoot modularization, and focuses on three important concepts of SOFABoot modularization: The basic concepts of SOFABoot module lookup and refresh, JVM Service and component management, and Root Application Context are introduced to help users quickly understand the implementation principle of SOFABoot modularization. At the end of the article, we also give practical suggestions to help users quickly get started with SOFABoot modularization.


The article mentions:


  • Open source | in the Spring integration in the Boot SOFABoot class separation ability

  • Open source | SOFABoot isolation principle analysis

  • Real fuck | SOFABoot based on modularization development

  • Ant Financial’s business system modularization —- modular isolation scheme


Long click attention to get distributed architecture dry goods

Welcome to jointly create SOFAStack https://github.com/alipay