One, foreword

The last article did some preparatory work, and this article is actually starting to write code.

Upgrading to a cluster is an easy thing to do once the single-instance architecture is in place, so put the single-instance and the cluster together in this article.

Ii. Single project architecture

Before we begin, let’s give some definitions of the terms in this article.

  • Organization (org) : This is what a company means. A company organization may have multiple projects under it.
  • Projects: Projects are internally self-consistent, and third-party calls are made between projects and their invocations. For example, the e-commerce backend mentioned in this article is one project, and organizing common libraries is another project, and each project has its own life cycle.
  • Application: An application is generally in the form of a domain service. It may be a service module in a single application or a microservice in a microservice architecture.

2.1 Organize common class libraries

This two-party library is usually at the organizational level, which encapsulates the common methods, configuration, and utility classes that all projects may use. Note the difference from the common libraries in the project, and the design of these libraries should pay attention to universality.

Some project-level proprietary configurations and tools should not be included here.

You can organize maven modules as springBoot sources do, or you can simply subcontract them.

Here are some of the most frequently needed configurations for the Web:

Unified return BaseResult, a generic return object using the interface layer paradigm is very important.

public class BaseResult<T> {
    /**
     * 返回状态
     */
    private boolean success;
    /** * returns the status code */
    private String code;
    /** * Returns a message */
    private String message;
    /** * returns data */
    privateT data; .Copy the code

Cross-domain configuration, note here @ ConditionalOnWebApplication web applications to take effect.

/** * <p> * Cross-domain configuration * </p> **@author robbendev
 */
@ConditionalOnWebApplication
@Configuration
public class GlobalCorsConfig {


    @Bean
    public CorsFilter corsFilter(a) {
        //1. Add CORS configuration information
        CorsConfiguration config = new CorsConfiguration();
        // Which primitive fields are allowed
        config.addAllowedOrigin("*");

        // Whether to send Cookie information
        config.setAllowCredentials(true);
        // Which primitive fields are allowed (request mode)
        config.addAllowedMethod("*");
        // Which raw fields are allowed (header information)
        config.addAllowedHeader("*");

        config.setMaxAge(3600L);
        // Which headers to expose (because cross-domain access does not get all headers by default)
        config.addExposedHeader("Content-Type");
        config.addExposedHeader("X-Requested-With");
        config.addExposedHeader("accept");
        config.addExposedHeader("Origin");
        config.addExposedHeader("Access-Control-Request-Method");
        config.addExposedHeader("Access-Control-Request-Headers");

        //2. Add a mapping path
        UrlBasedCorsConfigurationSource configSource = new UrlBasedCorsConfigurationSource();
        configSource.registerCorsConfiguration("/ * *", config);

        //3. Return a new CorsFilter.
        return newCorsFilter(configSource); }}Copy the code

Common business exceptions, which are generally thrown by Web applications at the business layer, are manually thrown by global exception capture and then converted to a common return value.

/** * @author robbendev */ @equalSandHashCode (callSuper = true) @data public Class BizException extends RuntimeException implements Serializable {/** * Serializable */ private static final Long serialVersionUID = -4636716497382947499L; /** * error code */ private String code; /** * error message */ private String message; /** * private Object data; }Copy the code

The backup stream (RequestBakRequestWrapper will not be attached) will be used in the interceptor.

/** * Wrap request parameters **@author robbendev
 */
@ConditionalOnWebApplication
@Component
@ServletComponentScan
@WebFilter(filterName = "requestBakFilter")
public class RequestBakFilter implements Filter {

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {}@Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest servletRequest = (HttpServletRequest) request;
        RequestBakRequestWrapper requestWrapper = new RequestBakRequestWrapper(servletRequest);
        chain.doFilter(requestWrapper, response);
    }

    @Override
    public void destroy(a) {}}Copy the code

Other configurations have best practices that vary from company to company.

2.2 Project common class library

This common library is project-level, and each different project will have its own custom common library requirements within the project.

If you need web development you need Springboot-Web or something like that, that’s defined here.

Project depend on

shop-common/pom/xml

<parent>
    <groupId>com.robbendev</groupId>
    <artifactId>robbendev-shop-backend</artifactId>
    <version>1.0 the SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

<artifactId>shop-common</artifactId>
<packaging>jar</packaging>


<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
</dependencies>.Copy the code

The user login

User login is a relatively independent module, single carry a section.

  • Spring Security + JWT solution
  • Server session.

You can search for OAuth2.0 and some single sign-on solutions.

For the shop project, the user login token is issued through the server session. The corresponding service definition is in shop-common. Stick a token local cache simple implementation

@Service
public class TokenServiceImpl implements TokenService {

    private Map<String, Token> session = new ConcurrentHashMap<>();

    @Override
    public void save(Token token) {
        session.put(token.getToken(), token);
    }

    @Override
    public void remove(String token) { session.remove(token); }}Copy the code

Interested students can try out how to implement expiration caching.

File service

No more code, it also belongs to the shop-common module, and each cloud service provider provides sample code. Note the interface implementation in separate form, shallow encapsulation in the project.

other

There are also application level configuration classes, interceptors, log handling, and so on. Without Posting the code, these practices are now mature.

2.3 Application Module Organization

How to organize the business module of our project to have a better scalability?

  1. Business modules are all housed in a Maven module, organized by subcontracting.

In this way, modules are organized by subcontracting, but because there is no strong constraint on the architecture level, it is easy to mix the methods of each module together, and it is not easy to split in the later stage.

  1. With maven’s modular organization, each module introduces the interfaces of other business modules, and each business module implements its own business methods.

It is obvious that the second method has a good extensibility in the background of large projects:

  • The decoupling between modules is realized.
  • If it is a single application deployment, it is only packaged together. If it is a microservice, it introduces the service layer framework and deplores each module separately. Easy to upgrade.
  • Avoid introducing too many complex components early in the project, yet have the ability to scale quickly. Upgrade as needed.

Robbendev-shop-backend

├ ─ ─ boot / / aggregate all the single application startup module module │ ├ ─ ─ pom. The XML │ └ ─ ─ the SRC │ ├ ─ ─ the main │ └ ─ ─ the test ├ ─ ─ the build / / specifies the packing order here │ ├ ─ ─ pom. The XML ├ ─ ─ pom. The XML ├ ─ ─ shop - common / / project public libraries │ ├ ─ ─ pom. The XML │ └ ─ ─ the SRC └ ─ ─ shop - modules / / project module split ├ ─ ─ pom. The XML ├ ─ ─ shop - market / / marketing module │ ├ ─ ─ │ ├─ Shop-Orders │ ├─ Interfaces │ ├─ Shop-Orders │ ├─ Interfaces │ ├─ Shop-Orders │ ├─ Interfaces │ ├─ Shop-Orders │ ├─ Interfaces │ ├─ Shop-Orders // │ ├─ ├─ shop-product │ ├─ product-interfaces │ ├─ Shop-product │ ├─ product-interfaces │ ├─ shop-product │ ├─ pop.xml │ ├─ product-interfaces │ ├─ shop-product │ ├─ pop.xml │ ├─ product-interfaces ├─ ├─ customs.txt ├─ customs.txt ├─ customs.txt ├─ customs.txt ├─ customs.txt ├─ customs.txt ├─ customs.txt ├─ customs.txt User-service // User serviceCopy the code

You can see that the different modules are organized by modules, with each business module communicating with the other modules through the IneterFaces module.

2.4 Application Architecture

Application architecture methodology

Now let’s look at how the individual application modules are organized. The methodology for building individual applications is now quite mature

  1. Classic three-tier architecture – Controller, Service, DAO and Entity

This can easily swell the service layer to thousands of lines per class, and each method can become a transactional script.

The upside is that it’s more intuitive, faster to write, and easier to read code. Disadvantages: The service layer is too bloated and the business meaning of the code is not strong.

  1. DDD modeling – interfaces, Application, InfraStruture, Domain

This can refer to relevant books, here do not repeat. I myself still prefer this kind of, also began to be popular slowly now. Some of the core concepts include aggregation, warehousing, domain services, domain events, application services, and so on.

Domain object modeling is a methodology that helps build a self-consistent application, not an architectural one. However, since the idea of domain object modeling is similar to that of microservices, the domain object method can be used to guide the separation of microservices. In fact, the separation of microservices is the division of business modules and bounded contexts.

Complete domain modeling is difficult to implement, especially in entity state management and domain event traceability. So instead of copying the concept of domain object modeling completely in real development, I will post my own domain object modeling practice.

First, the interface implementation separation, adding the binary library dependency version to the unified binary library dependency pom.xml we mentioned earlier

Post the POM of market-service:

/ /...<dependency>
    <groupId>com.robbendev</groupId>
    <artifactId>robbdendev-common</artifactId>
    <version>${robbendev-common.version}</version>
</dependency>

<dependency>
    <groupId>com.robbendev</groupId>
    <artifactId>shop-common</artifactId>
    <version>${robbendev-shop-backend.version}</version>
</dependency>

<dependency>
    <groupId>com.robbendev</groupId>
    <artifactId>market-interfaces</artifactId>
    <version>${robbendev-shop-backend.version}</version>
</dependency>

<dependency>
    <groupId>com.robbendev</groupId>
    <artifactId>orders-interfaces</artifactId>
    <version>${robbendev-shop-backend.version}</version>
</dependency>

<dependency>
    <groupId>com.robbendev</groupId>
    <artifactId>product-interfaces</artifactId>
    <version>${robbendev-shop-backend.version}</version>
</dependency>.Copy the code

This allows us to access the methods of other modules through the interface.

Here is the subcontracting of a single module. In fact, a single business can continue to be decouped by modules. However, considering that the business complexity in the early stage of the project is not very large, it is still only subcontracting for hierarchical processing. Subcontracting of order module based on domain object modeling:

├ ─ ─ the orders - interfaces │ ├ ─ ─ pom. The XML │ └ ─ ─ the SRC │ ├ ─ ─ the main │ │ ├ ─ ─ Java │ │ │ └ ─ ─ com. Robbebdev. Shop. The order │ │ │ ├ ─ ─ dto / / module interface parameter │ │ │ │ ├ ─ ─ request / / into the parameter definition │ │ │ │ └ ─ ─ the response / / a parameter definition │ │ │ └ ─ ─ service / / module service interface ├ ─ ─ the orders - service │ ├ ─ ─ Pom. XML │ └ ─ ─ the SRC │ ├ ─ ─ the main │ │ ├ ─ ─ Java │ │ │ └ ─ ─ com. Robbendev. Shop. The order │ │ │ ├ ─ ─ application / / application service layer │ │ │ ├ ─ ─ │ ├─ ├─ ├─ pop.xml │ ├─ ├─ ├─ pop.xmlCopy the code

You can see that there are two Maven modules: the interfaces module, which contains the module interface and parameter definitions, and the Service module, which implements the service interface methods in the user interface layer. The other layers are similar to a DDD project.

Business code

Post a demo interface implementation, using the order module as an example, now write an update order interface.

├ ─ ─ the orders - interfaces │ └ ─ ─ the SRC │ ├ ─ ─ the main │ │ ├ ─ ─ Java │ │ │ └ ─ ─ com │ │ │ └ ─ ─ robbendev. Shop. The order. The dto │ │ │ │ ├ ─ ─ Request │ │ │ │ │ └ ─ ─ FindOrderReq. Java │ │ │ │ └ ─ ─ the response │ │ │ │ └ ─ ─ FindOrderResp. Java │ │ │ └ ─ ─ service │ │ │ └ ─ ─ IOrderApi. Java ├ ─ ─ the orders - service │ └ ─ ─ the SRC │ ├ ─ ─ the main │ │ ├ ─ ─ Java │ │ │ └ ─ ─ com │ │ │ └ ─ ─ robbendev │ │ │ └ ─ ─ shop │ │ │ └ ─ ─ the order │ │ │ ├ ─ ─ application │ │ │ │ ├ ─ ─ IOrderService. Java / / application service interface │ │ │ │ └ ─ ─ IOrderServiceImpl. Java / / application service implementation class In fact, here can not use the interface, but compatible with some people's development habits. │ │ │ ├ ─ ─ domain │ │ │ │ ├ ─ ─ the Order. The Java / / entity │ │ │ │ └ ─ ─ OrderRepository. Java / / storage interface │ │ │ ├ ─ ─ infrastucture │ │ │ │ ├ ─ ─ Dataobject │ │ │ │ │ └ ─ ─ OrderDO. Java / / data object │ │ │ │ ├ ─ ─ mapper │ │ │ │ │ └ ─ ─ OrderMapper. Java / / data interface │ │ │ │ └ ─ ─ │ │ ├ ─ ├ ─ ├ ─ ├ ─ ├ ─ ├ ─ ├ ─ ├ ─ Java // The exposed external API that needs to implement the IOrderApi in the interfaces packageCopy the code

Module to module communication API

/** * <p> * Module communication API, specific implementation in the user interface layer. * </p> * *@author robbendev
 * @since 2021/4/1 5:07 下午
 */
public interface IOrderApi {

    BaseResult<FindOrderResp> findOrder(FindOrderReq req);
}

Copy the code

Application service

/** * <p> * Application services, this is the shallow layer, can be used as the domain layer of the facade, entity to out parameter conversion here. * </p> * *@author robbendev
 * @since 2021/4/1 5:35 下午
 */
@Service
public class IOrderServiceImpl implements IOrderService {

    @Resource
    OrderRepository orderRepository;


    @Override
    public FindOrderResp findOrder(FindOrderReq req) {
        Order order = orderRepository.findById(req.getId());
        FindOrderResp findOrderResp = new FindOrderResp();
        findOrderResp.setAmount(order.getAmount());
        findOrderResp.setProductName(order.getProductName());
        findOrderResp.setId(order.getId());

        returnfindOrderResp; }}Copy the code

entity

/** * entity, aggregate, aggregate root! Refer to DDD for concepts. Something like ID can be implemented with the Primitive Domain, like this. * <code>private OrderId id; </code> * *@author robbendev
 * @since 2021/4/1 5:14 下午
 */
@Data
public class Order {

    private Long id;
    private BigDecimal amount;
    private String productName;
}

Copy the code

Storage interface

/** * <p> * storage interface, concept reference DDD, can have multiple implementations, db implementation, ES implementation, etc. * </p> * *@author robbendev
 * @since2021/4/1 5:25pm */
public interface OrderRepository {

    Order findById(Long id);
}

Copy the code

The data object

/** * <p> * Data object, and database table field one-to-one mapping. * </p> * * @author robbendev * @since 2021/4/1 5:16pm */ @data public class OrderDO {private Long ID; private BigDecimal amount; private String productName; }Copy the code

Database access interface

/** * <p> ** @author robbendev * @since 2021/4/1 5:27 PM */ @mapper public interface OrderMapper { @Select("select * from order where id =#{id}") OrderDO getById(Long id); }Copy the code

Realization of warehousing

/** * <p> * </p> * * @author robbendev * @since 2021/4/1 5:25 PM */ @Component public class OrderRepositoryDBImpl implements OrderRepository { @Resource OrderMapper orderMapper; @Override public Order findById(Long id) { OrderDO orderDO = orderMapper.getById(id); // Object conversion alternatives mapsStruct or beanUtils. // There is a solution for entity state tracking, but it is complicated. // Therefore, DDD selection does not need to be completely suitable. Order order = new Order(); order.setId(orderDO.getId()); order.setAmount(orderDO.getAmount()); order.setProductName(orderDO.getProductName()); return order; }}Copy the code

The user interface

/** * <p> * User interface (DDD) API * </p> **@author robbendev
 * @since 2021/4/1 5:13 下午
 */
@RestController
@RequestMapping("/order")
public class OrderController implements IOrderApi {

    @Resource
    IOrderService orderService;

    @Override
    @PostMapping("/findOrder")
    public BaseResult<FindOrderResp> findOrder(@RequestBody FindOrderReq req) {
        FindOrderResp resp = orderService.findOrder(req);
        returnBaseResult.success(resp); }}Copy the code

Database DDL and configuration files are not written, a springBoot default database configuration.

2.5 Single Application Startup

Take a look at the BUILD module package project POM configuration before integration, because pay attention to the package order.

<parent>
    <artifactId>robbendev-shop-backend</artifactId>
    <groupId>com.robbendev</groupId>
    <version>1.0 the SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

<artifactId>build</artifactId>

<packaging>pom</packaging>

<modules>
    <module>../shop-common</module>
    <module>../shop-modules/shop-market/market-interfaces</module>
    <module>../shop-modules/shop-orders/orders-interfaces</module>
    <module>../shop-modules/shop-product/product-interfaces</module>
    <module>../shop-modules/shop-user/user-interfaces</module>
    <module>../shop-modules/shop-market/market-service</module>
    <module>../shop-modules/shop-orders/orders-service</module>
    <module>../shop-modules/shop-product/product-service</module>
    <module>../shop-modules/shop-user/user-service</module>
    <module>../boot</module>
</modules>

Copy the code

You can see that the project common libraries are packaged first. (According to the previous concept, organizing the release of the common libraries is a separate project and should have a separate life cycle.) , then package the module interface, and finally package the module application. So you don’t come up and say, “Oh, what did you do? I can’t find this file again. “

Look at the POM file and code of the Boot module

<parent> <artifactId>robbendev-shop-backend</artifactId> <groupId>com.robbendev</groupId> 1.0 the SNAPSHOT < version > < / version > < / parent > < modelVersion > 4.0.0 < / modelVersion > < packaging > jar < / packaging > <artifactId>boot</artifactId> <dependencies> <dependency> <groupId>com.robbendev</groupId> <artifactId>market-interfaces</artifactId> </dependency> <dependency> <groupId>com.robbendev</groupId> <artifactId>market-service</artifactId> </dependency> <dependency> <groupId>com.robbendev</groupId> <artifactId>orders-interfaces</artifactId> </dependency> <dependency> <groupId>com.robbendev</groupId> <artifactId>orders-service</artifactId> </dependency> //... Product user </dependencies>Copy the code

Then inside the Boot module, a few lines of code can run a SpringBoot Web application

/**
 * <p>
 *
 * </p>
 *
 * @author robbendev
 * @since 2021/3/31 2:43 下午
 */
@SpringBootApplication
public class AppBoot {

    public static void main(String[] args) { SpringApplication.run(AppBoot.class, args); }}Copy the code

Screenshot of successful Operation

2021-04-01 16:40:33.987  INFO 9926 --- [           main] com.robbendev.shop.AppBoot               : Starting AppBoot on huluobindeMacBook-Pro.local with PID 9926 (/Users/huluobin/IdeaProjects/robbendev-shop-backend/boot/target/classes started by huluobin in /Users/huluobin/IdeaProjects/robbendev-common)
2021-04-01 16:40:33.991  INFO 9926 --- [           main] com.robbendev.shop.AppBoot               : No active profile set, falling back to default profiles: default
2021-04-01 16:40:34.856  INFO 9926 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8080 (http)The 2021-04-01 16:40:34. 9926-868 the INFO [main] o.a pache, catalina. Core. StandardService: Starting the service [Tomcat] 2021-04-01 16:40:34. 869 INFO 9926 - [the main] org. Apache. Catalina. Core. StandardEngine: Starting Servlet engine: [Apache Tomcat/9.0.37] 2021-04-01 16:40:34.969 INFO 9926 -- [main] O.A.C.C.C. [Tomcat].[/] : Initializing Spring Embedded WebApplicationContext 2021-04-01 16:40:34.970 INFO 9926 -- [main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: Initialization completed in 887 ms 16:40:35 2021-04-01. 9926-150 the INFO [main] O.S.S.C oncurrent. ThreadPoolTaskExecutor : Initializing ExecutorService 'applicationTaskExecutor' 2021-04-01 16:40:35.301 INFO 9926 -- [main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started onport(s): 8080 (http)With the context path '2021-04-01 16:40:35. 309 INFO 9926 - [the main] com robbendev. Shop. AppBoot: Started AppBoot in 1.944seconds (JVM running for 3.03)
Copy the code

Then post the interface we just did, and everything is ok.

Well, here we have built the framework of the whole project, and now we can carry out business development according to the modules.

Third, the cluster

Distributed Session

Previously, our token was used as a local cache, so in the case of clustering, different requests may fall on different instances, resulting in cache invalidation. Solution:

  • One copy of each instance. It’s kind of wasteful.
  • When requesting, follow certain routing rules to ensure that it lands on the same machine each time. A little bit of a problem
  • Separate out the session. This requires that the global cache be stable.

Here’s the third option, which is more mainstream. Take a look at the redis implementation

@Service
public class TokenServiceRedisImpl implements TokenService {
    @Resource
    StringRedisTemplate stringRedisTemplate;

    @Override
    public void save(Token token) {
        stringRedisTemplate.opsForValue().set(token.getToken(), JsonUtilByFsJson.beanToJson(token)
                , 1, TimeUnit.DAYS);
    }

    @Override
    public void remove(String token) {
        stringRedisTemplate.delete(token);
    }
}
Copy the code

Then switch in your login service.

Load balancing

With the help of Kubernetes features, we can easily achieve horizontal expansion and load balancing.

Just change this to the number of extensions you want, and the Kubernetes service will load automatically.

Or change to yml

spec:
  progressDeadlineSeconds: 600
  replicas: 1  // Change the number of copies
  revisionHistoryLimit: 10
Copy the code

summary

This article mainly covers a Java backend from 0 to 1 and then to a cluster. Mainly some engineering practice and methodology, but also some of my own practice process.

After the clustering in the service layer, I will continue to talk about some practices in the data layer, such as data source repository, middleware repository repository table, etc. Finally, I will talk about microservices. The style is similar to this article.

Give a thumbs up for those of you who think you have gained something. Clap brick or contact me [email protected]