Backend Development 15 min read

Flexible Switching Between Monolith and Microservices in Tencent Docs: Architecture, Challenges, and Benefits

This article details how Tencent Docs implements a flexible architecture that can switch between monolithic and microservice deployments, describing the motivations, such as framework diversity, configuration conflicts, global variable management, hidden bugs, the custom 'monolith' tool with its configuration format, and the resulting performance and resource savings.

Architect
Architect
Architect
Flexible Switching Between Monolith and Microservices in Tencent Docs: Architecture, Challenges, and Benefits

Tencent Docs faced the need to choose between monolithic and microservice architectures for different business scenarios, such as large‑scale C‑end usage and private deployments. Recognizing that no single architecture is a silver bullet, the team pursued a flexible solution that can dynamically switch between the two.

Monolithic services integrate all functions into a single application, offering simplicity and ease of maintenance for small‑to‑medium projects, but they can suffer from scalability and deployment challenges as the codebase grows. Microservices decompose an application into independent services, providing decoupling, isolation, and elasticity for large projects, yet they introduce complexity, higher operational costs, and increased resource consumption.

In private‑cloud scenarios, maintaining over 100 microservices incurred high runtime costs (each running its own tRPC‑Go runtime), deployment overhead (updating numerous tad description files), and image distribution burdens (total image size exceeding 10 GB). To mitigate these issues, Tencent Docs designed a strategy to merge selected microservices into a few monolithic services while preserving the advantages of microservices where appropriate.

The core of this strategy is a custom tool named monolith , which reads a configuration file that maps monolith modules to the microservices they should merge. An example configuration looks like:

modules:
  - name: monolith-module1
    merged_servers:
      - name: microserver1
        ...
      - name: microserver2
        ...
  - name: monolith-module2
    merged_servers:
      - name: microserver3
        ...
      - name: microserver4
        ...

Several challenges were addressed during this transition:

Challenge 1: Diverse Frameworks

After years of evolution, services used various frameworks (tRPC‑Go, tRPC‑Cpp, SPP, LSF). The team standardized on the tRPC‑Go framework, limiting the merge scope to services that follow the agreed conventions.

Challenge 2: Inconsistent Configurations

Microservice and monolith configurations differed, especially in plugin and Redis settings. A unified configuration component was introduced, and configuration files were merged, for example:

plugin:
  config:
    app:
      providers:
        serverA:
          app.yaml:
            foo: bar
            foz: baz

and the monolith version:

plugin:
  config:
    app:
      providers:
        serverA:
          app.yaml:
            foo: bar
            foz: baz
        serverB:
          app.yaml:
            zoo: bar
            zoz: baz
        ...

Plugin configurations that could cause conflicts were refactored to a service‑scoped structure:

plugin:
  type-foo:
    name-bar:
      service1:
        key1: value1
        key2: value2
      service2:
        key1: value2
        key2: value1
      ...

Challenge 3: Global Variable Modifications

Modifying global variables (e.g., http.DefaultServerCodec , restful.Marshaller ) in a microservice context is safe, but in a monolith it can affect all merged services. Two strategies were adopted: developing common plugins to encapsulate such changes, and isolating modules that require distinct global settings.

Challenge 4: Hidden Bugs

Some bugs only surface after merging services. An illustrative example:

func Register(s *server.Server) {
  someserverpb.RegisterSomeServerSomeService(s, newSomeServiceImpl())
}

In a monolith, this registration can overwrite previously registered services, leading to 404 errors. The team uses a binary‑search‑style “divide‑and‑conquer” debugging method to isolate the offending service.

Challenge 5: Change Management

Automated checks are run on every merge request to ensure merged services still satisfy configuration and code‑structure constraints. Future plans include integrating interface and end‑to‑end tests.

The monolith tool generates a concise registration template that aggregates all standardized services:

// Code generated by backend/tools/monolith, DO NOT EDIT.
// Package service rpc入口层。
package service

import (
    {{- range .MergedServers}}
    {{.}}service "docx/backend/application/{{.}}/service"
    {{- end}}
    "git.code.oa.com/trpc-go/trpc-go/server"
)

// Register 注册 pb service 实现。
func Register(s *server.Server) {
    {{- range .MergedServers}}
    {{.}}service.Register(s)
    {{- end}}
}

Performance measurements on a representative module show substantial gains: binary size reduced from 264 MB to 68 MB (≈74 % saving), memory usage from 1722 MB to 670 MB (≈61 % saving), CPU usage from 6.43 cores to 4.99 cores, and average request latency improved slightly. Extrapolating to the entire system predicts a 75 % reduction in total image size and a 96 % reduction in total memory consumption.

In summary, by implementing a flexible architecture that can merge microservices into monolithic services when advantageous, Tencent Docs retains the scalability and isolation benefits of microservices while dramatically lowering resource consumption and improving performance.

PerformancearchitectureMicroservicessoftware engineeringconfigurationmonolithtRPC
Architect
Written by

Architect

Professional architect sharing high‑quality architecture insights. Topics include high‑availability, high‑performance, high‑stability architectures, big data, machine learning, Java, system and distributed architecture, AI, and practical large‑scale architecture case studies. Open to ideas‑driven architects who enjoy sharing and learning.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.