Tencent Docs: Flexible Switching Between Monolithic and Microservice Architectures
Tencent Docs built a configurable “monolith” tool that merges selected tRPC‑Go microservices into a few monolithic modules for private‑cloud deployments, overcoming framework, configuration, global‑state, and bug‑resolution challenges, and achieving up to 75 % image‑size reduction and 96 % memory savings while preserving microservice advantages where needed.
Software architecture has no silver bullet; good design requires continuous iteration. Tencent Docs faces business challenges and has implemented a flexible architecture that can switch between monolithic and microservice modes, offering a reference for similar projects.
Monolithic vs microservice: monolith integrates all functions into a single application, simple for small projects but suffers scalability as size grows. Microservices split into independent services, offering decoupling and resilience, suitable for large projects but increase complexity, deployment, and cost.
Tencent Docs needs to balance these in C‑end (large user base) and private‑cloud scenarios (smaller user base). To leverage advantages of both, they adopt a flexible strategy that can merge multiple microservices into a few monolithic services when needed.
In private‑cloud deployments, keeping >100 microservices would cause high runtime, deployment, and image distribution costs:
High runtime cost: each microservice runs its own tRPC‑Go runtime, consuming memory/CPU.
High deployment cost: maintaining tad descriptor files for each service.
High image distribution cost: total image size >10 GB.
Therefore, a solution is needed to merge microservices into monoliths without redeveloping functionality.
The goal: keep microservices on the TKEx platform for C‑end, but merge them into a few monoliths for private‑cloud.
They built a tool called monolith that uses a configuration file to specify which microservices are merged into which monolithic modules. Example configuration:
modules:
- name: monolith-module1
merged_servers:
- name: microserver1
...
- name: microserver2
...
- name: monolith-module2
merged_servers:
- name: microserver3
...
- name: microserver4
...The configuration describes the output monolith services and the source microservices. Automation integrates detection and generation of monolith services into each MR.
Challenges encountered:
3.1 Challenge 1 – Diverse frameworks
Various frameworks (tRPC‑Go, tRPC‑Cpp, SPP, LSF) were used historically. After a year of refactoring, most services standardized on tRPC‑Go, so only those services are merged.
3.2 Challenge 2 – Heterogeneous configurations
Different teams had custom plugins and config files, causing conflicts when merged. They unified configuration via a plugin component. Example microservice config:
plugin:
config:
app:
providers:
serverA:
app.yaml:
foo: bar
foz: bazMonolith config:
plugin:
config:
app:
providers:
serverA:
app.yaml:
foo: bar
foz: baz
serverB:
app.yaml:
zoo: bar
zoz: baz
...Plugin configuration conflicts were resolved by nesting service‑specific settings:
plugin:
type-foo:
name-bar:
service1:
key1: value1
key2: value2
service2:
key1: value2
key2: value1
...3.3 Challenge 3 – Global variable modifications
Modifying global variables (e.g., http.DefaultServerCodec, restful.Marshaller) is safe in isolated microservices but risky in a monolith. Strategies:
Develop common plugins (e.g., a CGI protocol) to encapsulate changes.
Isolate modules that need specific global changes.
3.4 Challenge 4 – Hidden bugs
Some bugs only appear after merging. Example Go registration code:
func Register(s *server.Server) {
someserverpb.RegisterSomeServerSomeService(s, newSomeServiceImpl())
}In a monolith, this can overwrite previously registered services, leading to 404 errors.
3.5 Challenge 5 – Change management
Automated pipelines check each MR for compliance with merge constraints (configuration compatibility, code structure). Future plans include integration tests and end‑to‑end tests.
After addressing challenges, the tool generates a concise template that registers all merged services:
// Code generated by backend/tools/monolith, DO NOT EDIT.
// Package service rpc entry layer.
package service
import (
{{- range .MergedServers}}
{{.}}service "docx/backend/application/{{.}}/service"
{{- end}}
"git.code.oa.com/trpc-go/trpc-go/server"
)
// Register registers pb service implementations.
func Register(s *server.Server) {
{{- range .MergedServers}}
{{.}}service.Register(s)
{{- end}}
}Performance comparison between monolithic and microservice deployments shows significant savings:
Metric
Monolith
Microservice
Saving %
Binary size (MB)
68
264
0.74
Memory (MB)
670
1722
0.61
CPU (cores)
4.99
6.43
0.22
Avg latency (ms)
9.3
9.8
0.05
Overall, total image size could drop by 75 % and total memory usage by 96 % after full monolithization, while retaining microservice benefits where appropriate.
Tencent Cloud Developer
Official Tencent Cloud community account that brings together developers, shares practical tech insights, and fosters an influential tech exchange community.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.