Why Our Team Switched from Node.js to Go: Lessons in Backend Engineering
This article details how a high‑traffic trading app migrated from Node.js to Go, outlining Go's advantages, drawbacks, and the team's engineering practices—including environment management, dependency handling, efficiency tools, standardized libraries, testing, monitoring, and distributed tracing—to achieve robust, high‑performance backend services.
Guangfa EasyGold APP is the core mobile trading application of Guangfa Securities, handling market data, trading, and wealth management for over 100 million daily requests and billions in transaction volume.
Before 2017 the team primarily used Node.js, benefiting from JavaScript's flexibility and a rich module ecosystem, but faced reliability issues, a single‑threaded model unsuitable for CPU‑intensive tasks, and difficulty diagnosing CPU/memory problems.
1. Switching to Go
The decision to adopt Go was driven by leadership, starting with the mobile securities configuration service and evolving into a full framework tailored to business needs.
Key advantages of Go identified by the team include:
Comprehensive toolchain (formatting, documentation, profiling, testing, race detection, memory analysis) that supports engineering management.
Simple, clean syntax that reduces code‑review friction and maintenance cost.
Native goroutine support for high concurrency without manual thread‑pool management.
Fast compilation and hot‑reload development speed.
Code Decay
Go’s minimalistic language design helps prevent code decay, as it provides only essential syntax, discouraging overly complex implementations that increase hand‑off and debugging costs.
Preventing code decay involves establishing coding standards, pair programming, and thorough code reviews.
2. Go’s Drawbacks
The team notes two main shortcomings: lack of generics (requiring repetitive type assertions or reflection) and implicit interface implementation, which can make type checks harder without IDE support, though the latter also offers flexibility across projects.
3. Engineering Practices
1) Environment Management
Services are containerized with Docker, and six environments are defined: local, CI (unit‑test), test, dev, UAT (pre‑release), and prod. The development workflow moves code from local development through CI, automated testing, dev debugging, UAT release, and finally production deployment.
2) Dependency Management
Initially using Glide to vendor dependencies, the team later adopted a two‑stage approach: project‑level version files (vendor.conf) plus a custom tool (godep) that mirrors packages to a GitLab group, combined with a cached “trash” utility to accelerate CI installs, reducing install time from ~14 minutes to ~30 seconds.
3) Efficiency Tools
The team built a CLI suite called gogen that provides scaffolding, automated dependency handling, code generation from Swagger, documentation generation, and load‑testing commands.
4) Library Standardization
Common third‑party Go modules are standardized and wrapped with internal tooling to inject Prometheus metrics and unified monitoring dashboards, covering HTTP (gin), gRPC, sessions, cron jobs, mocking, testing, Prometheus client, Redis, MongoDB, Postgres, Kafka, and Jaeger tracing.
5) Unit Testing
Unit tests are mandatory for all client‑facing APIs, must be deterministic, and ensure the main workflow works. Tests are integrated with go test, supporting race detection and memory profiling. Mocking is used for external services, and helper functions provide seeded data for MongoDB and Postgres.
6) Monitoring & Incident Diagnosis
Logs are formatted with a seelog‑based framework, and CI pipelines run full test suites before deployment. Production metrics are collected via Prometheus and visualized in Grafana, with alerts sent through WeChat. Service profiling starts on launch, and pprof endpoints aid post‑mortem analysis.
7) Distributed Tracing
The team adopted Jaeger for end‑to‑end tracing across services written in Go, Node.js, Java, and Python, leveraging its simple deployment, Cassandra/Elasticsearch storage options, and CNCF‑aligned OpenTracing support, while noting the lack of multi‑tenant features.
Conclusion
The article summarizes the team’s experience with Go, covering its strengths and weaknesses, environment and dependency management, efficiency tooling, library standardization, testing strategy, monitoring, and tracing solutions, providing a comprehensive view of building a robust, high‑performance backend system.
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.
