# Service Consolidation

> Merging multiple microservices into one without breaking consumers. Reduce operational overhead while keeping modular code.

## When to Use

- Too many services for the team size (each service = deployment + monitoring + env config overhead)
- Services share the same database and auth
- Services have low request volume individually
- You're spending more time on infrastructure than features

## The Pattern

```
Before: 4 services, 4 ports, 4 deployments
┌────────────┐ ┌────────────┐ ┌────────────┐ ┌────────────┐
│ Billing    │ │ Growth     │ │ Platform   │ │ Tracker    │
│ :4002      │ │ :4001      │ │ :4003      │ │ :4004      │
└────────────┘ └────────────┘ └────────────┘ └────────────┘

After: 1 service, 1 port, modules preserved
┌─────────────────────────────────────────────────────────┐
│ Platform Service :4003                                   │
│  ├── modules/auth/        (from platform)                │
│  ├── modules/subscriptions/ (from billing)               │
│  ├── modules/stripe/       (from billing)                │
│  ├── modules/referrals/    (from growth)                 │
│  ├── modules/items/        (from tracker)                │
│  └── modules/public/       (from tracker)                │
└─────────────────────────────────────────────────────────┘
```

## Step-by-Step

### 1. Inventory what exists

List all services with their modules, endpoints, and test counts:

| Service          | Port | Modules                             | Endpoints | Tests |
| ---------------- | ---- | ----------------------------------- | --------- | ----- |
| platform-service | 4003 | auth, audit, flags                  | 15        | 80    |
| billing-service  | 4002 | subscriptions, stripe, usage, plans | 12        | 40    |
| growth-service   | 4001 | invitations, referrals, promos      | 9         | 25    |
| tracker-service  | 4004 | items, comments, votes, public      | 11        | 35    |

### 2. Move modules (not rewrite)

The key insight: **move the module directories intact**. Don't rewrite code.

```bash
# Move billing modules into platform-service
cp -r billing-service/src/modules/subscriptions/ platform-service/src/modules/
cp -r billing-service/src/modules/stripe/ platform-service/src/modules/
cp -r billing-service/src/modules/usage/ platform-service/src/modules/
cp -r billing-service/src/modules/plans/ platform-service/src/modules/
```

### 3. Register routes in the consolidated service

```typescript
// platform-service/src/server.ts
// Existing modules
await server.register(import('./modules/auth/routes.js'), { prefix: '/api' });
await server.register(import('./modules/audit/routes.js'), { prefix: '/api' });

// Modules from billing-service
await server.register(import('./modules/subscriptions/routes.js'), { prefix: '/api' });
await server.register(import('./modules/stripe/routes.js'), { prefix: '/api' });
await server.register(import('./modules/usage/routes.js'), { prefix: '/api' });

// Modules from growth-service
await server.register(import('./modules/referrals/routes.js'), { prefix: '/api' });
await server.register(import('./modules/invitations/routes.js'), { prefix: '/api' });

// Modules from tracker-service
await server.register(import('./modules/items/routes.js'), { prefix: '/api' });
await server.register(import('./modules/public/routes.js'), { prefix: '/api' });
```

### 4. Register Cosmos containers

```typescript
// cosmos-init.ts
const containers = [
  // Existing
  { id: 'users', partitionKey: { paths: ['/productId'] } },
  { id: 'audit_log', partitionKey: { paths: ['/productId'] } },

  // From billing
  { id: 'subscriptions', partitionKey: { paths: ['/userId'] } },
  { id: 'usage_records', partitionKey: { paths: ['/userId'] } },

  // From growth
  { id: 'invitations', partitionKey: { paths: ['/productId'] } },
  { id: 'referrals', partitionKey: { paths: ['/userId'] } },

  // From tracker
  { id: 'tracker_items', partitionKey: { paths: ['/productId'] } },
  { id: 'comments', partitionKey: { paths: ['/itemId'] } },
];
```

### 5. Update all consumers

Every consumer that pointed to the old service URL must now point to platform-service:

```diff
# .env files in dashboards
- BILLING_SERVICE_URL=http://localhost:4002
- GROWTH_SERVICE_URL=http://localhost:4001
- TRACKER_SERVICE_URL=http://localhost:4004
+ PLATFORM_SERVICE_URL=http://localhost:4003
```

### 6. Run all tests

```bash
# All tests must pass in the consolidated service
pnpm test  # Should show combined test count (80 + 40 + 25 + 35 = 180)
```

### 7. Delete old services

Only after all tests pass and consumers are updated:

```bash
rm -rf services/billing-service/
rm -rf services/growth-service/
rm -rf services/tracker-service/
```

### 8. Update documentation

- Docker Compose (remove old service entries)
- AGENTS.md (update service inventory)
- Environment variable docs
- Health check scripts

## When NOT to Consolidate

- Services have different scaling requirements
- Services use different databases
- Services are maintained by different teams
- Services have conflicting dependency versions

## Real-World Result

| Metric      | Before                 | After                       |
| ----------- | ---------------------- | --------------------------- |
| Services    | 4                      | 1                           |
| Ports       | 4002, 4001, 4003, 4004 | 4003                        |
| Deployments | 4 Docker containers    | 1 Docker container          |
| Env files   | 4 sets of config       | 1 set                       |
| Test suites | 4 separate runs        | 1 unified run (847 tests)   |
| Modules     | Same count             | Same count (code preserved) |

The module pattern (types → repository → routes) made this consolidation smooth — each module is self-contained, so moving directories was trivial.

## Anti-Patterns

- **Rewriting during consolidation** — Move code, don't rewrite it
- **Consolidating before modularizing** — If code isn't modular, fix that first
- **Removing tests** — All tests from all services must survive the merge
- **Not updating consumers** — Stale URLs cause 502/ECONNREFUSED errors
- **Keeping old service directories** — Delete them to prevent confusion

## Related Skills

- [13 — Module Pattern](./13-module-pattern.md)
- [14 — Shared Package Extraction](./14-shared-packages.md)
- [17 — Multi-Repo Coordination](./17-multi-repo-coordination.md)
