Source code design#
Design Patterns and Jargon#
From time to time, people ask how to quickly and systematically learn about a domain. In the modern world, most knowledge is publicly accessible. But the annoying thing is that the documentation is all there, the source code is there, every word in the documentation is understood, and every line in the source code seems to be understood. But it is still quite difficult to grasp the mechanics of a system as a whole. Even if you narrow down the scope to learning a software system, or just say, Envoy.
Envoy is an open-source software, and the documentation is very detail that is rare in open-source projects; C++14 is written in a way that is much more approachable than C and C++1998. The readability is close to that of Java, and there are not as many acronyms as when looking at the Linux kernel. But like any other software source code, Envoy has its own design patterns and jargon, and understanding the jargon makes understanding source code more easy.
Callback design pattern#
Gang of Four’s (GoF) Design Patterns does not have a design pattern called Callback
. The Callback design pattern
is actually a variant or application of the Observer pattern
.
The Observer pattern solves the following problems:
Uncoupling one-to-many dependencies between objects to avoid tight coupling of objects.
Automatically update an unlimited number of dependent objects when an object changes state.
An object can notify multiple other objects.
Above is an explanation of the Observer pattern
from Wkipedia. I think essentially the observer pattern is more of a design pattern that uses agnosticism
to invert object dependencies.
In Envoy, its main purpose is to allow subsystems to be designed independently of each other. Envoy makes extensive use of this Callback Design Pattern
and has its own naming convention. This is the hurdle to cross in order to understanding Envoy code.
There are two subsystems above, Network::TransportSocket
and Network::Connection
. If every subsystem that uses Network::TransportSocket
had to rely on Network::TransportSocket
for notifications, the result would be a circular dependency. The Callback design pattern avoids this problem.
Subsystems#
Envoy is designed to be modular, with many subsystems that are independent of each other at compile time. These subsystems interact with each other through explicit dependencies and dependency inversion methods such as Callback glue. In general, each subsystem has its own C++ namespace (several related subsystems share a namespace). The main subsystems are listed below:
Buffer
- the buffer blockApi
- operating system callsConfig
- Configurations such as XDS.Event
- Event-drivenHttp
- HTTP relatedHttp::ConnectionPool
- HTTP connection pooling relatedHttp1
- HTTP/1.1 relatedHttp2
- HTTP/2 related
Network
- IP/TCP/DNS/Socket layer, i.e. OSI L3/L4 related. IncludesEnvoy Network Filter
,Listener
,Network::Address
andListener
.Network::Address
- IP address related
Server
- Envoy’s implementation of the Daemon lifecycle as a service.Stats
- monitoring metricsTcp
- TCP and connection pooling relatedUpstream
- Upstream related load balancing, health checks, etc.``
Most of these subsystems have their own functional C++ Class/Interface
, such as ReadFilter
for Envoy Network Filter
, and their accompanying Callback interface definitions, such as ReadFilterCallbacks
.