Preface

Recently, we are doing a permission control function, one of the services is to freeze the user, the specific business logic is not necessarily appropriate to elaborate, so we will use the “elephant in the freezer” to abstract instead. The process is as follows:

1
2
3
1. Open the refrigerator door
2. Put the elephant inside
3. Close the door of the refrigerator

There are two service providers to rely on:

  1. Fridge keepers (opening and closing doors)
  2. Moving company (moving elephants into the refrigerator)

So how to design the service invocation pattern?

Standards-based calls

In the olden days, the service provider, and the caller would agree together on a standard for incoming and outgoing messages (xml, json), according to which the service provider would publish http services, and the caller would invoke the provider’s http services according to this incoming and outgoing standard. Here the incoming and outgoing conventions, as well as the http protocol itself, are a standard. That is, both parties organize the service invocation relationship with a common standard to be followed.

API-based invocation

In the era of microservices, the service provider exposes its API interface to the public and hides the concrete implementation logic. After the service caller introduces the API of the other party, it can invoke the service of the other party. The service invocation relationship is organized in the way of API dependency.

SPI-based calls

In the era of middle office, we advocate a big middle office and a small front office. The middle office is responsible for realizing the reusable processes and capabilities involved in the business of “loading elephants into the refrigerator”, reserving SPI expansion points for the corresponding capabilities, and the business side provides different services by implementing the corresponding spi expansion points. For example, some businesses want to use porters to move elephants into the refrigerator, while others want to use cranes to move elephants into the refrigerator, and this can be achieved by implementing different logic in the spi extension points.

The essential difference between the three

Here is the definition of Zhang Qunhui (Hui Zi)

  1. “Standard” means the agreement that both parties follow. (For example, the http protocol, or the definition of incoming and outgoing parameters in http calls. Based on message decoupling actually also belongs to this category)
  2. interface implementer has an interface is the API model, an API generally only one implementation, to provide a kind of ability. (For example, a moving company provides the ability to move an elephant into a refrigerator)
  3. The user of the interface owns the interface is the SPI model, the user of the interface first defined the interface, the implementer according to the definition of the interface to achieve specific logic, an SPI can have more than one implementation. (For example, the middle platform architecture defined to move the elephant into the refrigerator SPI expansion point, can be achieved for the porter to move, can also be achieved for the crane to move)

Three trade-offs

The permission control function I developed has no possibility to become a permission control middleware, and the concept of middleware is basically dying now, so I first abandoned the middleware SPI extension design pattern.
So if we use the API dependency pattern, it will be as follows.

  1. The service provider publishes its own API
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    /***
    * Movers
    */
    public interface MovingCompany{
    void move(Elephant elephant);
    }

    /***
    * Refrigerator manager
    */
    public interface RefrigeratorManager{
    void open();
    void close();
    }
  2. The caller introduces the other party’s service and then calls it in order to achieve the elephant in the fridge
1
2
3
4
5
void putElephant2Refrigerator(elephant){
refrigeratorManager.open();// Open the refrigerator door
movingCompany.move(elephant);// Put the elephant in
refrigeratorManager.close();// Close the refrigerator door.
}

What is the problem with this calling pattern?

  1. If one day the business requires to clean the elephant before loading it into the fridge, then I need to modify the code to introduce a service to clean the elephant and then release it to take effect.
  2. If one day, a foreign company thinks this “loading elephants into the refrigerator” feature is good and wants to buy this service and deploy it to their company, it’s not possible, because this feature strongly depends on the refrigerator manager and handling company I currently introduced, I need to change the code to replace the dependent refrigerator manager and handling company to their local ones.

In the end, the “load elephant into fridge” feature uses a “standard” based call pattern.
All the service providers that need to participate in the “load the elephant into the fridge” are required to implement the following interfaces to provide the service according to the standard (incoming list and outgoing list) (interface names and method names are optional)

1
Result<Void> putElephant(String xxxId,String xxxType,String ext);
  1. Service nodes provide service metadata, drop configuration library, used for subsequent service initialization
1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
"operateType":"XXX",
"order":"1",
"interface":"com.xxx.service",
"method":"putElephant",
"Url":"xxx.xxx.xxx.net:12220",
"uniqueId":"xxx",
"argTypes":[
"java.lang.String",
"java.lang.String",
"java.lang.String"
],
"description":"Move the elephant into the refrigerator"
}
  1. When the service is executed, the configuration library is read, the service is dynamically initialized according to the order of the service node metadata, and the service node is invoked in generalization (dubbo generalization invocation, sofa generalization invocation, etc.).

Returning to the two problematic points of the API call pattern, this pattern has the following benefits.

  1. The service nodes involved in “putting the elephant in the fridge” are all configured in the database and dynamically initialized at execution time. When dealing with the business change of “cleaning the elephant before putting it in”, only the service nodes for cleaning the elephant need to be configured in the configuration database, and no application release is required.
  2. When SAAS is needed and deployed to a brand new environment, the data model and code logic are reusable, and only the configuration of the nodes involved in the service orchestration needs to be replaced with those needed by the other party, such as replacing the refrigerator manager and the handling company with the other party’s local company (the new service must also comply with the interface standard), only the configuration library needs to be changed, and no code transformation is required.

Postscript

Different systems are designed to face different domain problems, and the solution will be different. At present, the API mode is still the main mode for inter-system invocation; the business system tied to the middle platform still uses the SPI mode to invoke the downstream API (personal one-sided understanding, no in-depth study of the middle platform). The “interface standard” service orchestration, generalized invocation mode, only for some special scenarios.