From Architecture to Code: Analysis of the Latest Trends in Software Development
This article is based on a webcast lecture delivered by Chen Libing (Leijuan), a senior technical expert from the Alibaba Cloud Native team. In this lecture, he talked about what he considered to be the trends in software development in terms of domain-driven design (DDD), Reactive, Service Mesh, and code intelligence.
Trends in Software Architecture and Design
In this April, InfoQ released a report on the trends in software architecture and design. In this report, InfoQ divided software architectures into four categories, which are innovators, early adopters, early majority, and late majority, as shown in the following figure from left to right. This report has shown that many technologies, such as microservices and DDD, have become popular and have gone mainstream in the software development field. For early majority and late majority, many architectures and designs are related to DDD, such as microservices, command query responsibility separation (CQRS), and event-driven architecture.
In this article, we will also explain why the technologies related to DDD occupied an important place in the InfoQ report. In addition, we will further describe other related technologies, including Service Mesh.
Domain-Driven Design (DDD)
There are various practice guides for Domain-Driven Design (DDD) available on GitHub, but we will not be covering them in detail in this article. According to the following figure, you can use DDD to address issues that range from architecture and design to code. Therefore, DDD is a popular method in the entire software development lifecycle. An important theoretical foundation that guides the classification of microservices is DDD. Previous discussions on DDD mainly focused on how to identify bounded contexts and how to use tactics and strategies. In this article, we will describe a mainstream trend, which is the combination of DDD and Reactive.
Previously, DDD included concepts such as layered architecture and event-driven design. However, when you divided a single application into multiple applications, DDD had a big problem in the communication between these applications. We used bounded context mapping in DDD to address the problems in the communication between two bounded contexts. The decoupling method in DDD that we mentioned earlier provided a theoretical foundation for message-based asynchronous communication. However, we lacked the specific technology stacks required for the implementation process. The specific implementation methods may vary by language, because some languages support pure asynchronous operations and may contain built-in coroutine support. Therefore, we will select different solutions in different technology stacks. This lecture focuses on the Java technology stacks, but please note that Reactive is not exclusive to Java.
The advantage of Reactive is that the communication between two bounded contexts becomes a technology stack that we can implement by using Reactive. Domain-Driven Design in Java (jDDD) is a new project that is implemented based on Spring Data. jDDD is also a software development kit (SDK), which is designed to enable developers to present DDD ideas in code by using SDK. As we mentioned earlier, microservices rely on bounded contexts to identify boundaries and divide applications. In addition, both could-native and Function as a service (FaaS) solutions include event-driven architecture, but the concept of event was put forward early in DDD. If your solution involves events or asynchronous operations, you must pay attention to the relationship between events and DDD. Currently, we have mature technologies in this aspect. Please also note that in the combination of DDD and CQRS, CQRS implements read/write splitting. For example, a MySQL database provides a primary/secondary mechanism, in which the primary and secondary databases deal with writing and reading respectively. Similarly, CQRS also assumes an important role in DDD. At present, DDD is a mainstream architecture and design method, which enables us to address some design issues.
DDD divides an application into multiple small bounded contexts (multiple applications. Let’s see how the applications can communicate with each other. Reactive has not only provided guidelines for communication between different applications, but also provided corresponding technical solutions. For example, Async supports fully asynchronous operations, which are based on asynchronous technologies. In addition, the observable mode, which was first put forward by Microsoft, is widely used in architecture and design. In this mode, if a table changes, other tables will change accordingly. Java programmers have noticed that Spring is emphasizing on Spring Reactive, and many technical solutions are related to Reactive. In addition, message and event-driven architectures provide the basis for designing FaaS and cloud-native solutions.
In the trend chart of InfoQ software architecture and design, you can see functional programming in the early adopters, Reactive programming in the early majority, and RSocket and Reactive Streams in the innovators. In this section, we will describe RSocket, which may be unfamiliar to you. If you pay attention to the official website of Spring, you may know that Spring V5.2 has already provided built-in support for RSocket. However, there are not many related technical documents. As we mentioned in the preceding section, Reactive provides a theoretical foundation for the communication model of DDD. Therefore, the RSocket protocol is used to design a communication model based on Reactive theoretical foundation.
If you understand the semantics of Reactive, you will know the backpressure mode. Backpressure is similar to circuit breaking protection, and both mechanisms are designed to address the throttling issue. If the consumers or the service provider of an application get overwhelmed due to sudden bursts of traffic, the application will undergo an avalanche or crash. In a message queue of the traditional mode, the message provider actively pushed messages to the consumers. At that time, the provider continued pushing messages even though the consumers were unable to consume them. If this was the case, the system would become unstable because the consumers got overwhelmed.
Kafka, which came out later, implements the pull mode. Consumers can pull as many messages as they need, because Kafka supports accumulation of messages. However, time intervals must be set in pull mode. If this is the case, there is a problem: When no message is sent, the system will keep pulling messages from the broker, causing waste of network resources. Therefore, the backpressure in Reactive is implemented by adding a switch to the original message push model. With this switch, the system will stop pushing messages when the number of messages has reached a certain value. The advantage of this active push mechanism is its high performance without pulling messages or saving message consumer offsets. In short, with the backpressure mechanism, you can set the maximum number of messages that consumers can pull. Therefore, you can improve the performance within the limits and protect the message consumers.
RSocket has provided four communication models: request, response, publish/subscribe, and log collection. After you divide an application by using DDD, the applications need to communicate with each other. RSocket has not only provided four models that cover almost all communication scenarios between applications, but also provided metadata push. You can perform all cluster changes and throttling by using metadata push.
RSocket also supports peer-to-peer communication. Traditional communication models use the client/server mode. However, RSocket does not distinguish between client and server, which means that both communicating parties can send messages to each other. The design model and concept of RSocket that we mentioned earlier aim to address various problems in the communication between applications in Reactive. Although RSocket has not been widely used, many technical communities already support this solution. For example, support for RSocket has been added to Spring V5.2. The released communication protocol is based on Reactive. However, if there are database operations or other operations that are not asynchronous, you must make some changes. Therefore, the support for Reactive has been improved in Spring Boot 2.3, which is released in this year. For example, Spring WebFlux can be used in the gateway layer to guarantee the reactive design.
The middleware, communication models, and NoSQL products support asynchronous operations and address high-concurrency issues. It has been a long time since these features were added to Spring. In addition, databases also assume a key role in software development. Every Java programmer must first set up connection pools when they access databases. We must take the database design into account and think about questions such as which connection pool can provide the highest performance. This is because if database access is not an asynchronous operation, you must establish multiple connections to address the concurrency issue. Therefore, support for asynchronous database operations has been added to R2DBC in Spring. Currently, Spring can support asynchronous database operations, but we still have to wait for some time. This is because R2DBC is a specification and driver like JDBC, but at present there is only one R2DBC-based framework, which is Spring Data R2DBC. In contrast, there are many JDBC-based upper-layer frameworks, such as Hibernate and MyBatis. Therefore, many developers in China prefer MyBatis. If you intend to implement asynchronous methods, you will basically apply a fully asynchronous pattern, which stretches from the gateway layer protocol that was initially connected to the NoSQL database and includes the storage file system. Currently, we have mature technologies in this aspect.
Back to the graph from InfoQ at the beginning of this article, we can see that most of the trends in software architecture and design are related to cloud native. Among these trends, concepts such as serverless, service mesh, HTTP/2, and gRPC have gained high popularity, and service mesh has occupied an important position. Typical service mesh architectures still use the typical sidecar pattern that is based on Istio and Envoy.
Distributed Application Runtime (Dapr), which came out later, is different from the “Istio and Envoy” architecture. An Envoy sidecar can be considered as a proxy that is designed to connect services. In contrast, a Dapr sidecar is more than a proxy. It can help developers do many things, such as implementing applications by using a new programming language. However, Kafka or NoSQL databases do not provide language-specific SDKs.
Even when these SDKs are provided, they are unstable. Therefore, your choices of new technologies are constrained. For example, the most stable SDKs for big data platforms, such as HBase and Hadoop, are related to Java. Dapr is a runtime that interacts well with external systems. For example, a Dapr sidecar can not only communicate with gRPC, but also communicate with Kafka and then transfer the data obtained from Kafka to gRPC. In addition, with Dapr, developers do not need to understand the underlying details of communication protocols.
This design is better than the “Istio and Envoy” architecture. This is because if a sidecar only acts as a proxy, a large amount of data processing work must be done on the client side. If you need to do this work on a proxy, you must use a Dapr sidecar instead, which is more complex. In addition, Alibaba has recently been attempting to implement RSocket Broker, which is built upon Reactive Mesh and is structurally different from sidecars. Most sidecars that are based on message-driven or event-driven architectures only need to send messages.
In contrast, RSocket Broker has provided a comprehensive package of communication protocols between applications. Therefore, you do not need to select other communication protocols. We recommend that you select one of the preceding three technical solutions according to your actual situation, because there is no absolutely correct technology selection. At present, however, the “Istio and Envoy” architecture has gained popularity among the majority of developers, because it has provided convenient Kubernetes integration and comprehensive infrastructure.
An enterprise may run a few core systems but along with long-tail applications. If ideal FaaS solutions were formerly unavailable, the enterprise might build a large and comprehensive system to integrate these small functions. This could lead to difficulty in modifying the code, cause many troubles, and impede technical innovation. The advantage of FaaS is that functions can be deployed at edge locations, such as content distribution network (CDN) edge nodes. In terms of communication, FaaS solutions mostly adopt message- and event-driven architectures, and many current communication protocols use gRPC. FaaS itself requires a quick response speed, so it has certain requirements for communication protocols. The gRPC interfaces are closer to the infrastructure layer, on which Reactive gRPC is built to allow you to manipulate gRPC through Reactive. However, when you write code, you may not know that the infrastructure layer is based on gRPC. Another trend in technologies is AsyncAPI. In fact, many frameworks that you use at work can directly generate OpenAPI from code, such as springdoc-openapi.
Code intelligence is also a trend in technologies. After you select one or more of the architectures that we mentioned earlier to meet the demands of enterprises, the next steps will involve code. In this section, we will focus on two points: code generation and hand-written code. Code generation, which can be likened to scaffolding, is the process that can quickly generate the framework without the need to write configuration items. It is currently a mature technology. You can use a variety of tools for code generation, such as IntelliCode, Codota, Kite, and various Cloud IDEs. Software development technologies are frequently changing, with new frameworks or technologies being released every four or five months. This is challenging for developers, who are concerned about efficiency. Therefore, even with the help of code AI, developers still rely to some extent on features of Cloud IDEs. Cloud IDEs can quickly catch up with the advancement of some technologies. Therefore, with Cloud IDEs, you can meet new customer demands in a short time.
Code Generation and IDE
At present, many code generation tools are available to help you address the framework generation issues. For example, the start.spring.io scaffolding is an extensible code framework generator. Code generation is a built-in annotation mechanism in Java. You may not have noticed, but this mechanism is used in many scenarios to generate high-performance code. In addition, many IDEs, such as JetBrains and VS Code, have integrated code generation tools to help developers quickly generate code frameworks.