Best practices of developing SpringCloud-based microservices

By | July 1, 2021

Nowadays, SpringCloud-based microservice development is becoming more and more popular, and various open-source projects on the Internet are emerging endlessly. We can refer to open source projects to implement many out-of-the-box functions in actual work, but we must abide by certain conventions and specifications.

This post combines some of the problems encountered in our actual development to sort out a practice specification for microservice development. Welcome everyone to give pointers.

Best Practices of Using Maven

  • All projects must have a unified parent module.
    • All microservice projects depend on this parent, which is used to manage dependent versions, Maven repositories, and jar versions for unified upgrade and maintenance.
    • There can be custom modules such as core, starter, rate-limit, etc., under the parent package.
  • The role of core package:
    • Various development specifications are agreed in the form of POJO; such as BaseEntity, unified input, and return various two-party and three-party components use AutoConfig out of the box;
    • Various help classes to improve development efficiency, etc. XXXUtil
    • Note: All dependent scopes of the core package must be provided to avoid transitive dependencies. At the same time, use Condition annotations to load Beans conditionally, such as @ConditionalOnClass(Ribbon.class), @ConditionalOnBean(StringRedisTemplete.class)
  • Starter module: If you need to rely on more than ten starters for each service, you can build a unified starter module to help them unify their dependencies, manage the dependency set, and simplify dependencies.
  • Rate-limit module: Used to place non-generic self-developed components
  • Correctly distinguish between the Release version and the Snapshot version.
    • Note: If it is a Snapshot version, it will be automatically published to the snapshot repository when mvn deploy, and if the snapshot version of the module is used, without changing the version number, when directly compiling and packaging, Maven will automatically download it from the mirror server The latest snapshot version.
    • If it is a Release version, it will be automatically released to the official version library when mvn is deployed, and the official version of the module is used. Without changing the version number, when compiling and packaging, if the module of this version already exists locally, it will not take the initiative Go to the mirror server to download.
    • In short:
      • Release: The official version, you can no longer use this version number if there are bugs
      • Snapshot: Snapshot version, if there are bugs, you can continue to use the same version number, you can automatically upgrade, it is recommended to use

Best Practices of Implementing Service Calls

  • By introducing SDK calls between services, service consumers need to rely on the API provided by the producer and cooperate with the snapshot to facilitate upgrades
account
    account-api
    account-service
  • The account-API module contains the things that the consumer needs, such as API interface, vo, input parameters, etc…
public interface AccountApi {
    ...
}
  • account-service implements the interface provided by account-api
@RestController
@Log4j2
@Api(tags = "user inteface")
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class AccountController implements AccountApi {
    ...
}
  • The consumer calls the producer through feign, directly integrates the interface provided by the producer and handles the circuit breaker
@Component
@FeignClient(name = "account-service",fallbackFactory = AccountClientFallbackFactory.class)
public interface AccountClient extends AccountApi {
    ...
}

@Component
public class AccountClientFallbackFactory implements FallbackFactory<AccountClient> {

    @Override
    public AccountClient create(Throwable throwable) {
        AccountClientFallback accountClientFallback = new AccountClientFallback();
        accountClientFallback.setCause(throwable);
        return accountClientFallback;
    }

}

@Slf4j
public class AccountClientFallback implements AccountClient {
    @Setter
    private Throwable cause;

    @Override
    public ResultData<AccountDTO> getByCode(String accountCode) {
        log.error("Query Failed" ,cause);
        AccountDTO account = new AccountDTO();
        account.setAccountCode("000");
        account.setAccountName("test account");
        return ResultData.success(account);
    }

}

Best Practices of Developing RESTful API

An API is a developer's UI-just like any other UI. It is essential to ensure that the user experience is carefully considered!

The following two formats can be used:

  • /Version/access control/domain objects
  • /Version/access control/domain objects/actions

Domain objects need to comply with the following constraints:

  • Domain objects use nouns instead of verbs
  • Use domain object names directly Use /ticket instead of plural /tickets
  • Domain object relationship expression does not exceed 2 levels at most, such as /ticket/12/message
  • Need to distinguish GET PUT POST DELETE request method correctly
  • What cannot be expressed by noun + request method can be expanded to /domain object/verb such as POST /user/login

Access control is performed on the interface at the gateway layer. The access control rules are divided into

  • pb-public All requests can be accessed
  • pt-protected Requires token authentication to be accessible
  • pv-private cannot be accessed through the gateway, can only be called internally by the microservice
  • df-default The gateway requests token authentication, and the request parameters and return results are encrypted and decrypted

Some issues regarding versioning:

  • Focus on microservices and upgrade the entire service

For example, a microservice has the following API

  • GET /v1/pb/user
  • POST /v1/pb/user
  • PUT /v1/pb/user

If POST /v1/pb/user needs to be upgraded, you need to upgrade the entire microservice /v1 to /v2, while ensuring that the old version of the compatible api can continue to access

GET /v2/pb/user is equivalent to GET /v1/pb/user

POST /v1/pb/user marked as obsolete

POST /v2/pb/user

PUT /v2/pb/user is equivalent to PUT /v1/pb/user

Code

  • The GET method {version} can be any value, both v1 and v2, such as: @GetMapping("/{version}/pb/user")
  • The POST method forces the use of V1 and marks it as obsolete, but it can still be used

Best Practices of Designing Microservice Gateway

  • Its own service can implement it without assuming the microservice authentication function (simple services can be directly authenticated at the gateway layer)
  • The difference between gateway authentication and microservice authentication is explained in detail in my other articles.
    Need to implement access control permissions, combined with the Restful specification above, block special requests such as /pv/**
  • There are features that require the CI with gray release
    When developing joint debugging, it is necessary to import server traffic to the local and combine the metadata and request header of nacos to realize the screening of service instances.