Practical articles: those "soft skills" of interface development

I often see people in technical groups or various blogs arguing about which API testing tool should be used, which http client, postman, apifox, jmeter, how to choose the right tool for you, how to improve productivity, faster After finishing the work at hand, in fact, in the final analysis, there is no best, only what suits you is the best. At different stages of interface development, you can choose the tools that suit you to achieve your goals faster.

CURD has been testing interfaces in the front and back offices for many years, testing interfaces across teams, and calling external interfaces in the background.

  • wrong interface format
  • wrong interface field
  • Interface missing fields
  • The interface is not standardized enough
  • The interface is not stable enough

So, how to avoid or save the cost of going back and forth in between?

design

  1. norm

    1. Determine the request method, commonly used GET POST, REST specification GET/POST/DELETE/PUT, other OPTIONS/PATCH/COPY/HEAD/LINK/UNLINK/PURGE/LOCK/UNLOCK/PROPFIND/VIEW

    2. Determine the request parameters, whether it is request param or request body, what is the type of request body

    3. Whether the interface needs security verification, and which verification method is used

    4. The return body structure. Generally speaking, the data returned by the backend will be encapsulated in an additional layer based on the standard HTTP STATUS, which is used to associate the business or the processing logic inside the background.

      {
          "code":200,
          "msg":"request succeeded",
          "data":{}
      }
      
  2. The CODE standard that agrees on the structure of the returned body, generally adopts the standard http status(200/400/401/403/500 is commonly used. In the actual development process, some developers will customize some http status codes, which need to be defined at the most Start, set global norms)

  3. The actual business data structure that agrees to return the body structure, in general, includes the following:

    type
    Field Name
    Is it mandatory
    type
    Chinese meaning

Documentation tool

The interface documentation tools I often use are swagger and RAP2. However, in actual work, some colleagues encountered, based on the developer's own habits, always think about the existing code, and only have the interface, which falls into a misunderstanding. The interface is not a matter for one person. In a big way, it belongs to the lifeline of the entire project operation process. In a small way, it is a "conflict game" process between the interface developer and the interface user.

Therefore, no matter what tool is used, it is the most efficient development method to define the interface fields in advance, and then debug each side separately. After completion, perform combined verification.

swagger

It is used for docking development between teams, what you see is what you get, and it can provide convenient query of actual data.

RAP2

It is generally used for cross-team and cross-departmental collaborative development work. It provides mock s, and front-end colleagues in the project team can directly call tests.

test and monitor

The interface docking stage is the most prone to disputes. Because individuals are working on government-related projects, generally speaking, the data of the development environment cannot simulate the formal environment 100%. When docking with other colleagues or teams, it often occurs. For this kind of problem, in the development and debugging stage, the interface does not conform to the specification. After finally adjusting the interface and getting the official environment, as soon as the data comes up, it is found that there are problems with the data standard provided by the other party.

Network isolation, slow VPN speed, and all kinds of back and forth are annoying.

So I gradually began to think, can I use the tools I already have to try to locate the problem as soon as possible and troubleshoot the problem as soon as possible? After a period of research, the following methodology was developed for the related problems in the interface docking process

External Protection -> POSTMAN tests

1. Add environment variables

Generally speaking, environment variables include IP, parameters, token s, etc., which are divided into two types, static variables and dynamic variables. Variables are generally placed in the Variables in the collection.

How to use: {{variable_name}}

  • static variable

    Static variables are generally the request parameters of the interface, which can be imported directly through double brackets

  • dynamic variable

    The most common dynamic variables are various verification parameters in the header, and tokens are the most common. Generally, the interface provider will provide an additional token request interface, which is used in conjunction with postman's Pre-request Scripts

    pm.test("Status code is 200", function () {
        pm.response.to.have.status(200);
    });
     
    // Convert responseBody to json string
    var data = JSON.parse(responseBody);
    // Set the environment variable token for reference by the following interface, the location is the location obtained in the previous step
    pm.environment.set("access_token", data.result.access_token);
    

2. Set up the test method

2.1 Global test

For all interfaces, the common thing is to return the body structure test, that is, to ensure that the code is correct and the data is not empty

Add Tests in the collection

pm.test("HTTP request 200", function () {
    pm.response.to.have.status(200);
});

var jsonData = pm.response.json();

pm.test("The interface returns 200", function () {
    pm.expect(jsonData.resultCode === 200).to.equal(true);
});

pm.test("The data returned by the interface is not empty", function () {
    if(Array.isArray(jsonData.result)){
        pm.expect(jsonData.result.length > 0).to.equal(true);
    }else{
        pm.expect(jsonData.result !== null).to.equal(true);
    }
});
2.2 Interface schema verification
  • json to json schema

    postman has a built-in Ajv JSON schema validator verification method. For convenience, we need to convert the json defined in the interface design phase into a json schema, and then verify it through pm.response.to.have.jsonSchema

    • For example, in the interface definition stage, the json simulation data is as follows

      {
        "resultCode": 200,
        "resultMsg": "success",
        "result": {
          "basicData": {
            "shipCode": "Standing with the Four Mountains",
            "shipNo": "But the fate and the wind",
            "shipRegNo": "The most distant mine is its calendar",
            "shipFirstregNo": "When the light is as high as any",
            "origShipRegNo": "Goutian Downs",
            "shipId": "she raises awareness",
            "origShipName": "The root water should be dry",
            "shipIdFlagCode": "ten births",
            "shipInspectNo": "Cloth off oil quick hot acid",
            "shipImo": "Experts are suitable for green",
            "shipMmsi": "Matie to Tujina",
            "shipCallsign": "Anwar day at the entrance",
            "shipName": "Xiang and other generation hand research",
            "shipNameEn": "connected to the decision",
            "origShipNameEn": "head treatment",
            "shipRegionFlagCode": "Ji Shijia Mao Dang cloth",
            "shipRouteCode": "More with Lin Jiqi",
            "sailAreaCode": "set eye real value",
            "regportCode": "Grid weight Ancheng speed",
            "origRegportName": "knot in the middle",
            "shipHullMaterialCode": "There are also many loaf bags",
            "shipTypeCode": "Equipped with direct security external energy",
            "shipValue": "line tanner",
            "shipLength": 39657,
            "shipBreadth": 56071,
            "shipDepth": 98131,
            "shipGrosston": 93962,
            "shipNetton": 55891,
            "shipDwt": "Strategist",
            "shipEngineTypeCode": "It has been recognized",
            "shipEngineNum": 5815,
            "shipEnginePower": 20505,
            "shipPropellerTypeCode": "Appropriately let go",
            "shipPropellerNum": 81206,
            "shipSlotNum": 63116,
            "shipParkNum": 23051,
            "shipPassengerNum": 91588,
            "shipSummerDraft": 1200,
            "shipWindLevel": "for phone calls",
            "shipMinFreeboard": 30004,
            "shipyard": "stoma release letter",
            "shipyardEn": "Misuans plan",
            "shipBuiltAddr": "Turn seven to see hair",
            "shipBuiltAddrEn": "Empty thousand law firm",
            "shipBuiltDate": "1983-05-14 10:06:54",
            "rebuiltShipyard": "Point often main anti-going south class",
            "rebuiltShipyardEn": "so the government has",
            "shipRebuiltAddr": "but six waters",
            "shipRebuiltAddrEn": "the first case",
            "icCardNo": "Nursing and listening dimension counting office",
            "origDeletionDate": "1991-06-28 12:11:21",
            "shipRebuiltDate": "1985-04-07 13:17:08",
            "statusFlagCode": "Mining City is pro-electric",
            "shipIdSealFlagCode": "say pro-change",
            "mortgageFlagCode": "Need a large semi-circle certificate soon",
            "bareboatFlagCode": "Also on the middle class",
            "alterFlagCode": "Jiang Yecha",
            "handoutCardFlagCode": "Di Wang Yuan",
            "financialLeaseFlagCode": "to the main controller",
            "hibernateFlagCode": "be mindful",
            "trialShipFlagCode": "choose hot",
            "detainFlagCode": "Type open account",
            "permanentSealRemark": "The King of Guangding Department",
            "orgCode": "It's alright",
            "shipRouteCodeCn": "list her you",
            "shipReginFlagCodeCn": "river",
            "sailAreaCodeCn": "Lian speed Lao Jiang she",
            "shipTypeCodeCn": "Inner nine labor customs",
            "shipRegionFlagCodeCn": "segment line",
            "regportCodeCn": "Fossil value research",
            "shipEngineTypeCodeCn": "High Goketa group",
            "shipPropellerTypeCodeCn": "quick action",
            "orgCodeCn": "business talker shape"
          }
        }
      }
      
    • Conversion via online tools, json schema conversion

      {
        "type": "object",
        "required": [],
        "properties": {
          "resultCode": {
            "type": "number"
          },
          "resultMsg": {
            "type": "string"
          },
          "result": {
            "type": "object",
            "required": [],
            "properties": {
              "basicData": {
                "type": "object",
                "required": [],
                "properties": {
                  "shipCode": {
                    "type": "string"
                  },
                  "shipNo": {
                    "type": "string"
                  },
                  "shipRegNo": {
                    "type": "string"
                  },
                  "shipFirstregNo": {
                    "type": "string"
                  },
                  "origShipRegNo": {
                    "type": "string"
                  },
                  "shipId": {
                    "type": "string"
                  },
                  "origShipName": {
                    "type": "string"
                  },
                  "shipIdFlagCode": {
                    "type": "string"
                  },
                  "shipInspectNo": {
                    "type": "string"
                  },
                  "shipImo": {
                    "type": "string"
                  },
                  "shipMmsi": {
                    "type": "string"
                  },
                  "shipCallsign": {
                    "type": "string"
                  },
                  "shipName": {
                    "type": "string"
                  },
                  "shipNameEn": {
                    "type": "string"
                  },
                  "origShipNameEn": {
                    "type": "string"
                  },
                  "shipRegionFlagCode": {
                    "type": "string"
                  },
                  "shipRouteCode": {
                    "type": "string"
                  },
                  "sailAreaCode": {
                    "type": "string"
                  },
                  "regportCode": {
                    "type": "string"
                  },
                  "origRegportName": {
                    "type": "string"
                  },
                  "shipHullMaterialCode": {
                    "type": "string"
                  },
                  "shipTypeCode": {
                    "type": "string"
                  },
                  "shipValue": {
                    "type": "string"
                  },
                  "shipLength": {
                    "type": "number"
                  },
                  "shipBreadth": {
                    "type": "number"
                  },
                  "shipDepth": {
                    "type": "number"
                  },
                  "shipGrosston": {
                    "type": "number"
                  },
                  "shipNetton": {
                    "type": "number"
                  },
                  "shipDwt": {
                    "type": "string"
                  },
                  "shipEngineTypeCode": {
                    "type": "string"
                  },
                  "shipEngineNum": {
                    "type": "number"
                  },
                  "shipEnginePower": {
                    "type": "number"
                  },
                  "shipPropellerTypeCode": {
                    "type": "string"
                  },
                  "shipPropellerNum": {
                    "type": "number"
                  },
                  "shipSlotNum": {
                    "type": "number"
                  },
                  "shipParkNum": {
                    "type": "number"
                  },
                  "shipPassengerNum": {
                    "type": "number"
                  },
                  "shipSummerDraft": {
                    "type": "number"
                  },
                  "shipWindLevel": {
                    "type": "string"
                  },
                  "shipMinFreeboard": {
                    "type": "number"
                  },
                  "shipyard": {
                    "type": "string"
                  },
                  "shipyardEn": {
                    "type": "string"
                  },
                  "shipBuiltAddr": {
                    "type": "string"
                  },
                  "shipBuiltAddrEn": {
                    "type": "string"
                  },
                  "shipBuiltDate": {
                    "type": "string"
                  },
                  "rebuiltShipyard": {
                    "type": "string"
                  },
                  "rebuiltShipyardEn": {
                    "type": "string"
                  },
                  "shipRebuiltAddr": {
                    "type": "string"
                  },
                  "shipRebuiltAddrEn": {
                    "type": "string"
                  },
                  "icCardNo": {
                    "type": "string"
                  },
                  "origDeletionDate": {
                    "type": "string"
                  },
                  "shipRebuiltDate": {
                    "type": "string"
                  },
                  "statusFlagCode": {
                    "type": "string"
                  },
                  "shipIdSealFlagCode": {
                    "type": "string"
                  },
                  "mortgageFlagCode": {
                    "type": "string"
                  },
                  "bareboatFlagCode": {
                    "type": "string"
                  },
                  "alterFlagCode": {
                    "type": "string"
                  },
                  "handoutCardFlagCode": {
                    "type": "string"
                  },
                  "financialLeaseFlagCode": {
                    "type": "string"
                  },
                  "hibernateFlagCode": {
                    "type": "string"
                  },
                  "trialShipFlagCode": {
                    "type": "string"
                  },
                  "detainFlagCode": {
                    "type": "string"
                  },
                  "permanentSealRemark": {
                    "type": "string"
                  },
                  "orgCode": {
                    "type": "string"
                  },
                  "shipRouteCodeCn": {
                    "type": "string"
                  },
                  "shipReginFlagCodeCn": {
                    "type": "string"
                  },
                  "sailAreaCodeCn": {
                    "type": "string"
                  },
                  "shipTypeCodeCn": {
                    "type": "string"
                  },
                  "shipRegionFlagCodeCn": {
                    "type": "string"
                  },
                  "regportCodeCn": {
                    "type": "string"
                  },
                  "shipEngineTypeCodeCn": {
                    "type": "string"
                  },
                  "shipPropellerTypeCodeCn": {
                    "type": "string"
                  },
                  "orgCodeCn": {
                    "type": "string"
                  }
                }
              }
            }
          }
        }
      }
      
    • Modify the specific definition of the above schema according to the actual situation of the interface

      For example, the string field can be empty

      Original definition:

      "orgCodeCn": {
           "type": "string"
      }
      

      After modification:

      "orgCodeCn": {
           "type": ["string","null"]
      }
      

      If the time format is YYYY-MM-DD

      Original definition:

      "origDeletionDate": {
          "type": "string"
      }
      

      After modification:

      "origDeletionDate": {
          "type": "string",
          "pattern":"(([0-9]{3}[1-9]|[0-9]{2}[1-9][0-9]{1}|[0-9]{1}[1-9][0-9]{2}|[1-9][0-9]{3})-(((0[13578]|1[02])-(0[1-9]|[12][0-9]|3[01]))|((0[469]|11)-(0[1-9]|[12][0-9]|30))|(02-(0[1-9]|[1][0-9]|2[0-8]))))|((([0-9]{2})(0[48]|[2468][048]|[13579][26])|((0[48]|[2468][048]|[3579][26])00))-02-29)"
      }
      

      For other attributes and related descriptions of the specific schema, please refer to JSON Schema Specification (Chinese Version)

      Note: The pattern uses regular expressions. Search for some commonly used regular expressions on the Internet, and then check them twice. There is no need to spend a lot of effort building the wheels yourself.

3. Batch run

  • Run the login interface first and update the token to the global variable

  • Make a cup of hot tea and run the entire collection

  • View the running results and know at a glance which interfaces have problems

4. Handling of different environments

To export the collection file to the actual environment of the intranet, you need to replace the global variable

Internal protection -> prometheus http buried point monitoring

In microservices, the gateway service can complete the monitoring and traffic detection of the interface in the system, but in many cases, for complex business systems, it is necessary to connect to external services and hardcode rest request processing.

How to ensure that the system can continuously observe the status and processing time of external interfaces? It is recommended to use springboot+prometheus to monitor the interfaces in the system, access exceptions, and cooperate with alertmanager+webhook to notify real-time alarms.

1.pom.xml add dependencies

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

<dependency>
	<groupId>io.micrometer</groupId>
	<artifactId>micrometer-registry-prometheus</artifactId>
	<version>1.8.5</version>
</dependency>

2. Configure restTemplate

Here, the default HTTP request of restTemplate in the system is first modified to OKHTTP

@Bean
public RestTemplate restTemplate(MeterRegistry registry) {

    // Configure OKhttpClient first, add eventListener to collect okhttpClient metrics
    OkHttpClient client = new OkHttpClient
        .Builder()
            .sslSocketFactory(SSLSocketClient.getSSLSocketFactory(), SSLSocketClient.getX509TrustManager())
            .hostnameVerifier(SSLSocketClient.getHostnameVerifier())
            .eventListener(OkHttpMetricsEventListener
                           	.builder(registry, "okhttp.requests")
                				.uriMapper(req -> req.url().encodedPath())
                				.tags(Tags.of("okhttp", "performance"))
                   		   	.build())
        .build();
    
    OkHttp3ClientHttpRequestFactory factory = new OkHttp3ClientHttpRequestFactory(client);
    // It can be configured through the configuration file, and the injection code is omitted.
    factory.setConnectTimeout(connectTimeout);
    factory.setReadTimeout(readTimeout);
    factory.setWriteTimeout(writeTimeout);
    return new RestTemplate(factory);
}

@Bean
public OkHttpClient okHttpClient(MeterRegistry registry) {
    return new OkHttpClient.Builder()
            .eventListener(OkHttpMetricsEventListener.builder(registry, "okhttp.requests")
                    .tags(Tags.of("okhttp", "performance"))
                    .build())
            .build();
}

3.micrometer configuration

@Configuration
public class MicroMeterConfig {
    @Bean
    public MeterRegistryCustomizer<MeterRegistry> meterRegistryCustomizer(@Value("${spring.application.name}") String applicationName) {

        return meterRegistry -> meterRegistry
                .config()
                 .commonTags(Collections.singletonList(Tag.of("application", applicationName)));
    }

    @Bean
    public TimedAspect timedAspect(MeterRegistry registry) {
        return new TimedAspect(registry);
    }
}

4. Add actuator exposure endpoint

management:
  endpoints:
    web:
      exposure:
        include: '*'

After the configuration is completed, it can be used in the traditional way

@Autowired
private RestTemplate restTemplate;

5.prometheus.yml add configuration

  - job_name: 'spring_grafana'
    scrape_interval: 5s
    scrape_timeout: 4s
    metrics_path: 'actuator/prometheus'
    static_configs:
      - targets: ['ip:port']

6.grafana add dashboard

Spring Boot 2.1 System Monitor

final effect

Link summary

  1. postman scripts
  2. JSON to SCHEMA online tool
  3. RAP2 interface document management
  4. grafana
  5. springboot uses Micrometer to integrate Prometheus monitoring

Tags: Java PostMan

Posted by jamesloi on Thu, 13 Oct 2022 23:47:34 +0300