Service Definition: The Cornerstone of Carousell’s Infrastructure Platform
How a single YAML file per service became the source of truth for automation, deployment, and self-serve provisioning across our group.
Co-Authored with: Praveen
In the previous post, we introduced the Group Infrastructure Platform and why Carousell Group needed a unified approach to build, deploy, and operate services across business units. This post dives into the foundational piece that makes everything else possible: Service Definition.
The Problem
At Carousell, knowledge about a service lived in multiple places. Who owns user-svc? Check GitHub. What datastores does it use? Grep the config files. Which BUs is it deployed to? Ask the SRE on-call. Managing this data in each BU was different, at times incomplete with missing pieces.
This scattered metadata created three concrete problems:
- Automation was hard. Every tool — CI pipelines, deployment scripts, monitoring — had to independently discover service properties, often by parsing scripts or hardcoded values.
- Catalog maintenance was a constant tax. Keeping services’ list, its metadata accurate required manual effort which made it harder to keep things up to date.
- Cross-BU deployment was not possible. There was no machine-readable way to express “this service should be deployed in Carousell, Cho Tot, and Mudah with these properties.”
The Solution: A Declarative Service Definition
We introduced a Service Definition — a YAML file per service that captures everything needed to provision, deploy, and operate that service. Think of it as the service’s birth certificate and operating manual in one.
Given a valid Service Definition, a functional service, with all its dependencies and infra components — can be provisioned automatically in any environment.
What’s in a Service Definition?
Here’s a complete example:
name: demo-gateway
system: demo-gateway # derived from name
scope:
- cs # Carousell
- md # Mudah
repo: github.com/carousell/demo-gateway
description: >
Service to demonstrate backend development
at Carousell. Playground for onboarding bootcamp.
owner: backendplatform # GitHub team slug
language: go
framework: orion-v2
api-definition-repo: https://github.com/carousell/shared-proto
api-definitions:
- demo/public/demo.proto
- demo/management/admin.proto
datastores:
- name: demo-db
type: postgresql
version: "12"
schema: migration/
components:
- type: app
name: demo-gateway
commands:
start: ["start", "demo-gateway"]
- type: worker
name: demo-kafka-consumer
commands:
start: ["start", "demo-kafka-consumer"]
dependencies:
- user/user
- cats/cats
externalDependencies:
- protocol: http
hosts:
- api-xyz.sendbird.comA few fields worth highlighting:
scope is the field that makes cross-BU deployment possible. It declares which business units this service targets, and every downstream system — CI/CD, Config Manager, infra provisioning — reads it to do the right thing.
datastores are declared by name, not by connection string. The name-to-host mapping lives in the infrastructure layer. Config Manager uses this to prepare connection configs per environment, so engineers never hardcode connection strings.
components let a service declare multiple deployable units — the main app plus workers (Kafka consumers, scheduled jobs) — all built from the same repo and image but started with different commands.
Get Rajath Ramesh’s stories in your inbox
Join Medium for free to get updates from this writer.
dependencies make inter-service relationships explicit and machine-readable, enabling service-mesh and NAT-based traffic policies.
Where Definitions Live
All Service Definitions across the group live in a single central repository, grouped by BU scope:
service-definition/
├── definitions/
│ ├── cs/ # Carousell services
│ ├── md/ # Mudah services
│ ├── ct/ # Cho Tot services
│ └── grp/ # Group-wide services
├── schema/ # Protobuf schema
└── tools/ # CLI validation toolsWe considered two placement approaches during the RFC process: definitions in each service repo vs. a central repo. We chose central because it gives a global view of every service in the group, makes cross-cutting queries trivial, and aligns with our vision of self-serve provisioning, where developers create and edit definitions without touching Git directly.
The trade-off is that infrastructure changes (like adding a new datastore) go through the central repo rather than the service repo. We accepted this because it means Config Manager and other downstream systems always have a consistent, validated source of truth.
Validation at the Source
Multiple systems depend on Service Definition, so correctness is non-negotiable. A CLI tool validates against the protobuf-backed schema:
$ service-definition validate -f definitions/grp/demo.yaml
✓ Schema valid
✓ Name uniqueness: passed
✓ Owner team exists in GitHub
✓ API definition paths resolveThis runs as a CI check on every PR. Invalid definitions never merge.
The schema itself is defined as a protobuf message, giving it a reliable evolution path with strong typing and backward compatibility guarantees.
How Platform Components Use It
The Service Definition is consumed by virtually every other component in the platform:
This is the key insight: every platform component reads from the same source, eliminating the divergence that happens when each tool independently discovers service properties.
Design Decisions
Three key decisions shaped the design, each documented through Architecture Decision Records (ADRs):
Workers as child properties of a service, not separate definitions. We chose this to maintain parity — the same code deployed across all components eliminates a whole class of version incompatibility errors. The trade-off is slower deployments when only a worker changes, but correctness won over speed here.
YAML format backed by protobuf schema. YAML is concise, human-readable, and supports complex data types. The protobuf schema provides reliable validation and a clear evolution path. Diffs in PRs are immediately understandable.
Schema evolution via ADR. The schema has been amended a few times as use cases and dependencies grew: renaming, standardising to match group naming conventions and restructuring the folder layout to group by BU scope.
What’s Next
Currently, Service Definition captures the essentials: metadata, APIs, datastores, components, and dependencies. Future milestones will extend it to include infrastructure provisioning specs (resource requests, autoscaling policies), method-specific overrides and more.
The vision: describe your service once, and the platform does the rest.
Next up, we cover how API contracts are defined, generated, and shared across BUs using our unified Protobuffer management pipeline.
← Previous: Part 1 — Common Tech Foundation → Next: Part 3 — API Definition
This is Part 2 of the Group Infrastructure Platform series on the Carousell Insider blog.











