12 From Application to Principle How to Efficiently Read the Sharding Sphere Source Code

12 From Application to Principle How to Efficiently Read the ShardingSphere Source Code #

Starting from this lesson, the column will enter the “ShardingSphere Source Code Analysis of Infrastructure” module. After introducing the core functions of ShardingSphere including database sharding, read-write splitting, distributed transactions, and data encryption, I will lead you to fully analyze the underlying implementation principles and mechanisms behind these core functions. We will achieve this goal by deeply analyzing the ShardingSphere source code.

How to Analyze the Code Structure of ShardingSphere Systematically? #

When reading open-source frameworks, we often encounter a big problem: we often get lost in the details of the code and fail to grasp the overall structure of the framework. Mainstream code frameworks that are widely known and widely used in the market are undoubtedly well-planned, and their code structure inevitably has a certain complexity. The same goes for ShardingSphere. We found that the first-level directory structure of the ShardingSphere source code has 15 directories, and these directories contain more than 50 specific Maven projects:

Drawing 0.png ShardingSphere Source Code First-Level Directory Structure

How can we quickly grasp the code structure of ShardingSphere? This is the first question we need to answer when analyzing the source code. Therefore, we need to develop a systematic approach to analyze the code structure of the ShardingSphere framework.

In this lesson, we will abstract the topic of how to systematically analyze the ShardingSphere code structure and provide six systematic methods to address this issue (as shown in the following figure):

Drawing 1.png

Next, we will expand on these methods based on the ShardingSphere framework.

Reading the Source Code Based on Scalable Design #

ShardingSphere adopts the microkernel architectural pattern in its design to ensure high scalability of the system and uses the SPI mechanism provided by the JDK to implement the microkernel architecture. In the root directory of the ShardingSphere source code, there is an independent project called shardingsphere-spi. Obviously, from the name, this project should include the code related to implementing the SPI of ShardingSphere. In this project, there is a TypeBasedSPI interface, which has a rich class hierarchy. Many core interfaces we will discuss later in this course, including the ConfigCenter for implementing the configuration center and the RegistryCenter for implementing the registration center, inherit from this interface, as shown below:

Drawing 3.png Class Hierarchy of the TypeBasedSPI Interface in ShardingSphere

The implementations of these interfaces follow the SPI mechanism provided by the JDK. When we read the code projects in ShardingSphere, once we find a file named after a service interface under the META-INF/services directory in the code project, it means that this code project contains the SPI definitions for extension.

In ShardingSphere, a lot of extensions are implemented using the microkernel architecture and the SPI mechanism. Once you understand the basic principles of the microkernel architecture and the implementation of SPI, you will realize that many code structuring methods in ShardingSphere are designed to meet the needs of these extensions. The way ShardingSphere implements the microkernel architecture is by directly encapsulating the JDK’s ServiceLoader class and adding custom functions such as property settings, without too much complexity in itself.

Of course, there are other manifestations of scalability besides the microkernel architecture. ShardingSphere also extensively uses callback mechanisms and various design patterns that support scalability. Mastering these mechanisms and patterns also helps to read the ShardingSphere source code more effectively.

Reading the Source Code Based on Package Design Principles #

Package design principles can be used to design and plan the code structure of open-source frameworks. For a package structure, the most important design points are high cohesion and low coupling. When we start reading the source code of a framework, in order to avoid focusing too much on details and only pay attention to a specific component, we can also use these principles to manage our learning expectations.

Taking ShardingSphere as an example, when analyzing its routing engine, we found two code projects: sharding-core-route and sharding-core-entry. From the perspective of code structure, although neither of these two code projects is directly targeted at business developers, sharding-core-route is a low-level component of the routing engine, which includes the core class ShardingRouter.

On the other hand, sharding-core-entry is at a higher level and provides the classes PreparedQueryShardingEngine and SimpleQueryShardingEngine. The package structure is shown below:

Drawing 4.png

In the figure, we can see a clear hierarchical relationship between the two code structures, which is a representative example of the package principle used in ShardingSphere, that is, organizing package structure based on the hierarchical level of classes.

Reading the Source Code Based on Basic Development Specification #

For ShardingSphere, there is a very good starting point for understanding its code structure, which is based on the JDBC specification. We know that ShardingSphere is designed to be fully compatible with the JDBC specification from the beginning, and the set of sharding operation interfaces it exposes is exactly the same as the interfaces provided in the JDBC specification. As long as you understand how to use the core interfaces in JDBC like DataSource, Connection, and Statement, you can easily grasp the code entry points exposed to developers in ShardingSphere and understand the overall structure of the framework.

Let’s take a look at an example in this regard. If you are just starting to work with the ShardingSphere source code, it can be somewhat challenging to find the entry point for SQL execution. In ShardingSphere, there is a factory class called ShardingDataSourceFactory specifically used to create ShardingDataSource. And based on the discussion in the article “Specification Compatibility: What is the Relationship between JDBC Specification and ShardingSphere?”, ShardingDataSource is an implementation class of the DataSource interface in the JDBC specification:

public final class ShardingDataSourceFactory {
    
    public static DataSource createDataSource(
            final Map<String, DataSource> dataSourceMap, final ShardingRuleConfiguration shardingRuleConfig, final Properties props) throws SQLException {
        return new ShardingDataSource(dataSourceMap, new ShardingRule(shardingRuleConfig, dataSourceMap.keySet()), props);
    }
}

Through this factory class, we can easily find the entry point for creating a DataSource that supports sharding, thus leading us to the underlying classes like ShardingConnection and ShardingStatement.

In fact, there are a group of DataSourceFactory factory classes and corresponding DataSource classes in ShardingSphere:

Drawing 6.png

When reading the ShardingSphere source code, the core interfaces provided by the JDBC specification and their implementation classes provide us with a way to efficiently understand the code entry points and organizational structure.

Reading the Source Code Based on Core Execution Process #

In fact, there is another relatively easy-to-understand method to help us understand the code structure, and that is the code execution process. Any system behavior can be considered as a combination of processes. Through analysis, what seems to be a complex code structure can generally be simplified into a main process that runs through the whole system. As long as we grasp this main process, we can understand the overall code structure of the framework.

So, what is the main process for the ShardingSphere framework? This question is not difficult to answer. In fact, the JDBC specification provides a basic development process for implementing data storage and access. We can start with DataSource, gradually introduce objects like Connection and Statement, and complete the main process of SQL execution. This is a main process outlined from the perspective of the core functionality provided by the framework.

In terms of the internal code organization structure of the framework, there is actually a concept of core process. The most typical example is the sharding engine structure of ShardingSphere, where the entire sharding engine execution process can be clearly divided into five components, which are parsing engine, routing engine, rewriting engine, executing engine, and merging engine:

Drawing 8.png

ShardingSphere has given clear names to each engine, and corresponding conventions have been made in the organization structure of the code project. For example, the sharding-core-route project is used to implement the routing engine; the sharding-core-execute project is used to implement the executing engine; the sharding-core-merge project is used to implement the merging engine, and so on. This is a main process outlined from the perspective of the internal implementation mechanism of the framework.

In the field of software modeling, there are tools and techniques available to visualize the code execution process, such as activity diagrams and sequence diagrams in UML. In the following lessons, we will use these tools to help you understand many code execution processes in ShardingSphere that are waiting to be explored.

Reading the Source Code Based on the Framework Evolution Process #

ShardingSphere has gone through the development from version 1.X to 4.X, and its functionality has become more and more abundant. The current code structure is already quite complex. But I believe that the developers of ShardingSphere didn’t design the code structure as it is now from the beginning. From another perspective, if we were to design such a framework ourselves, we would usually adopt a certain strategy to gradually implement and improve the framework, from simple to complex, from core functionality to auxiliary mechanisms. This is also a basic law of software development. In light of this, when we want to understand the code structure of ShardingSphere but feel lost, we can consider a key question: How to systematically break down the framework from easy to difficult? Actually, this question has been answered in the previous few lessons when introducing the core features of ShardingSphere. We first introduced the sharding feature, then expanded to read-write splitting, and finally to data masking. From the evolution of these features, we can deduce the evolution of the underlying code structure. Here, we explain this point using the implementation process of the data masking feature as an example.

In ShardingSphere, the implementation of the data masking feature is not independent but relies on the SQL rewrite engine. We can quickly go to the rewriteAndConvert method in the BaseShardingEngine class:

private Collection<RouteUnit> rewriteAndConvert(final String sql, final List<Object> parameters, final SQLRouteResult sqlRouteResult) {
    // Build SQLRewriteContext
    SQLRewriteContext sqlRewriteContext = new SQLRewriteContext(metaData.getRelationMetas(), sqlRouteResult.getSqlStatementContext(), sql, parameters);
    // Decorate SQLRewriteContext with ShardingSQLRewriteContextDecorator
    new ShardingSQLRewriteContextDecorator(shardingRule, sqlRouteResult).decorate(sqlRewriteContext);
    // Determine whether to query with cipher column
    boolean isQueryWithCipherColumn = shardingProperties.<Boolean>getValue(ShardingPropertiesConstant.QUERY_WITH_CIPHER_COLUMN);
    // Decorate SQLRewriteContext with EncryptSQLRewriteContextDecorator
    new EncryptSQLRewriteContextDecorator(shardingRule.getEncryptRule(), isQueryWithCipherColumn).decorate(sqlRewriteContext);
    // Generate SQLTokens
    sqlRewriteContext.generateSQLTokens();

    // ...
    return result;
}

Note that here, two SQLRewriteContextDecorator classes are implemented based on the decorator pattern: ShardingSQLRewriteContextDecorator and EncryptSQLRewriteContextDecorator. The latter decorates on top of the former. In other words, we can first use ShardingSQLRewriteContextDecorator alone to complete the rewriting operation on SQL.

With the evolution of the architecture, we can also add new features for data masking based on the original EncryptSQLRewriteContextDecorator. This reflects a process of architectural evolution. By reading these two decorator classes and the SQL rewriting context object SQLRewriteContext, we can better understand the design principles and implementation principles of the code:

Drawing 10.png

We will discuss the specific implementation details of data masking and the decorator pattern in detail in the article “Data Masking: How to Implement Low-invasive Data Masking Solution Based on the Rewrite Engine”.

Reading Source Code Based on Common External Components #

In the article “Foreword: How to Correctly Learn a Sharding Framework”, we put forward a point that the technical principles have commonality. This also helps us to better read the source code of ShardingSphere.

ShardingSphere integrates a group of excellent open-source frameworks, including ZooKeeper, Apollo, and Nacos for implementing the configuration center and registry center, SkyWalking for implementing distributed tracing, and Atomikos and Seata for implementing distributed transactions.

Let’s take distributed transactions as an example. ShardingSphere provides a code project called sharding-transaction-core to abstract distributed transactions. Then, for scenarios based on two-phase commit (2PC), it provides the sharding-transaction-2pc code project, and for flexible transactions, it provides the sharding-transaction-base code project. Inside the sharding-transaction-2pc code project, there are 5 sub-projects as shown below:

Drawing 12.png Sub-projects under the sharding-transaction-2pc code project When browsing through these code projects, you will find that there are very few classes in each project. The reason for this is that these classes are only responsible for integrating with third-party frameworks. So as long as we have a certain understanding of these third-party frameworks, reading this part of the code will be very simple.

For example, we know that ZooKeeper can be used for both configuration center and registry center. As a mainstream distributed coordination framework, its basic working principle is based on the temporary nodes and listener mechanism it provides. Based on this principle of ZooKeeper, we can register the various DataSources used by ShardingSphere to ZooKeeper, and dynamically govern the database instances based on the runtime status of the DataSources, as well as implement access circuit breaker mechanisms. In fact, ShardingSphere is able to achieve this by relying on the basic functions provided by ZooKeeper. As long as we grasp these functions, understanding this part of the code will not be difficult, and ShardingSphere itself does not use any complex features in ZooKeeper.

How to organize the core technical system in ShardingSphere? #

ShardingSphere includes many technical systems. In this course, we will discuss these technical systems from four aspects: basic architecture, sharding engine, distributed transaction, governance and integration.

Basic Architecture #

The standard for defining basic architecture here is that the technologies belonging to the basic architecture category can run independently of the ShardingSphere framework itself. In other words, these technologies can be extracted separately for use by other frameworks. We believe that the micro-kernel architecture and distributed primary key implemented by ShardingSphere can be classified as basic architecture.

Sharding Engine #

The sharding engine is the most core technical system of ShardingSphere, including parsing engine, routing engine, rewrite engine, execute engine, merge engine, and read-write splitting. We will elaborate on each of these topics. The sharding engine occupies the largest portion of the ShardingSphere source code analysis.

For the parsing engine, we focus on the various stages involved in the SQL parsing process. For the routing engine, we will introduce data access sharding routing and broadcast routing based on the basic principles of routing, as well as the implementation process of integrating multiple sharding strategies and algorithms during the routing process. The rewrite engine is relatively simple, and we will discuss how to implement the SQL rewriting mechanism based on the decorator pattern. For the execute engine, we first need to sort out and abstract the overall process of SQL execution in the sharding environment, and then grasp the Executor execution model in ShardingSphere. In the merge engine, we will analyze the data merging types and the implementation process of various merging strategies. Finally, we will focus on the implementation mechanism of read-write splitting in ordinary master-slave architecture and sharding master-slave architecture.

Distributed Transaction #

For distributed transactions, we need to understand the abstraction process of distributed transactions in ShardingSphere, and then systematically analyze the implementation principles of strong consistency and flexible transaction support based on various third-party frameworks in ShardingSphere.

Governance and Integration #

In governance and integration, from the perspective of source code discussion, the topics include data desensitization, configuration center, registry center, distributed tracing, and system integration.

For data desensitization, we will provide a low-intrusive data desensitization solution based on the rewrite engine. The configuration center is used to achieve dynamic management of configuration information, while the registry center implements a database access circuit breaker mechanism. These two technologies can be implemented using common frameworks, but they are oriented towards different business scenarios. We will analyze the common implementation principles and the differences in business scenario requirements. ShardingSphere implements a series of hook mechanisms. We will analyze the working mechanism of data access tracing based on these hook mechanisms and the OpenTracing protocol. Of course, as a mainstream open source framework, ShardingSphere also seamlessly integrates with Spring and Spring Boot. Analyzing the system integration can better help us use this framework.

From Source Code Analysis to Daily Development #

One of the main goals of this course is to help you understand the implementation principles of ShardingSphere by systematically explaining the framework source code. However, this is not the only goal. As an extension, we hope to learn the methods and techniques of system architecture design and implementation process through the study of this excellent open source framework, and guide daily development work. For example, in the next lesson when introducing the micro-kernel architecture, we will focus on describing how to implement system scalability based on the SPI mechanism provided by JDK, and this implementation mechanism can be fully applied to daily development process.

This is a process from source code analysis to daily development, and it is an evolving process. As the saying goes, theory guides practice. We need to grasp the principles behind the complex technical knowledge system and various emerging tool frameworks, and then use our own language and methods to explain these principles, that is, to build our own technical knowledge system.

Conclusion #

This lesson is the first lesson in the source code analysis part of ShardingSphere. We have explained six systematic methods for analyzing the ShardingSphere code structure, guiding you to read the ShardingSphere source code correctly from the perspectives of scalability, package design principles, basic development specifications, core execution process, framework evolution process, and common external components. At the same time, we have organized the various core technical systems that will be discussed in the subsequent courses based on the basic architecture of ShardingSphere itself and its business functions.

Here is a question for you to think about: in analyzing the various methods of ShardingSphere, can you give one or two specific examples for each method?

That’s all for the content of this lesson. From the next lesson onwards, we will enter the discussion of the basic architecture technology system in ShardingSphere, starting with the micro-kernel architecture and its implementation principles. Remember to join the class on time.