Get the FREE Ultimate OpenClaw Setup Guide →

service-layer-architecture

npx machina-cli add skill wpank/ai/service-layer-architecture --openclaw
Files (1)
SKILL.md
5.1 KB

Service Layer Architecture

Clean, performant API layers with proper separation of concerns and parallel data fetching.

Installation

OpenClaw / Moltbot / Clawbot

npx clawhub@latest install service-layer-architecture

When to Use

  • Building REST APIs with complex data aggregation
  • GraphQL resolvers needing data from multiple sources
  • Any API where responses combine data from multiple queries
  • Systems needing testable, maintainable code

Three-Layer Architecture

┌─────────────────────────────────────────────────────┐
│  Controllers   │  HTTP handling, validation        │
├─────────────────────────────────────────────────────┤
│  Services      │  Business logic, data enrichment  │
├─────────────────────────────────────────────────────┤
│  Queries       │  Database access, raw data fetch  │
└─────────────────────────────────────────────────────┘

Layer 1: Controllers (HTTP Only)

// controllers/Entity.ts
import { getEntity, getEntities } from "../services/Entity";

const router = new Router();

router.get("/entity/:entityId", async (ctx) => {
  const { entityId } = ctx.params;

  if (!entityId) {
    ctx.status = 400;
    ctx.body = { error: "Invalid entity ID" };
    return;
  }

  const entity = await getEntity(entityId);
  
  if (!entity) {
    ctx.status = 404;
    ctx.body = { error: "Entity not found" };
    return;
  }
  
  ctx.status = 200;
  ctx.body = entity;
});

Layer 2: Services (Business Logic)

// services/Entity.ts
import { queries } from "@common";

export const getEntityData = async (entity: RawEntity): Promise<EnrichedEntity> => {
  // Parallel fetch all related data
  const [metadata, score, activity, location] = await Promise.all([
    queries.getMetadata(),
    queries.getLatestScore(entity.id),
    queries.getActivity(entity.id),
    queries.getLocation(entity.slotId),
  ]);

  // Transform and combine
  return {
    ...entity,
    bonded: entity.bonded / Math.pow(10, metadata.decimals),
    total: score?.total ?? 0,
    location: location?.city,
    activity: {
      activeCount: activity?.active?.length ?? 0,
      inactiveCount: activity?.inactive?.length ?? 0,
    },
  };
};

export const getEntity = async (entityId: string): Promise<EnrichedEntity | null> => {
  const entity = await queries.getEntityById(entityId);
  if (!entity) return null;
  return getEntityData(entity);
};

export const getEntities = async (): Promise<EnrichedEntity[]> => {
  const all = await queries.allEntities();
  const enriched = await Promise.all(all.map(getEntityData));
  return enriched.sort((a, b) => b.total - a.total);
};

Layer 3: Queries (Database Access)

// queries/Entities.ts
import { EntityModel } from "../models";

export const allEntities = async () => {
  return EntityModel.find({}).lean();  // Always use .lean()
};

export const getEntityById = async (id: string) => {
  return EntityModel.findOne({ id }).lean();
};

export const validEntities = async () => {
  return EntityModel.find({ valid: true }).lean();
};

Parallel Data Fetching

// BAD: Sequential (slow)
const metadata = await queries.getMetadata();
const score = await queries.getScore(id);
const location = await queries.getLocation(id);
// Time: sum of all queries

// GOOD: Parallel (fast)
const [metadata, score, location] = await Promise.all([
  queries.getMetadata(),
  queries.getScore(id),
  queries.getLocation(id),
]);
// Time: max of all queries

Layer Responsibilities

TaskLayer
Parse request paramsController
Validate inputController
Set HTTP statusController
Combine multiple queriesService
Transform dataService
Sort/filter resultsService
Run database queryQuery

Related Skills


NEVER Do

  • NEVER put database queries in controllers — Violates separation
  • NEVER put HTTP concerns in services — Services must be reusable
  • NEVER fetch related data sequentially — Use Promise.all
  • NEVER skip .lean() on read queries — 5-10x faster
  • NEVER expose raw database errors — Transform to user-friendly messages

Source

git clone https://github.com/wpank/ai/blob/main/skills/backend/service-layer-architecture/SKILL.mdView on GitHub

Overview

Service Layer Architecture splits HTTP handling, business logic, and data access into Controllers, Services, and Queries. It enables data enrichment and parallel fetching to produce richer responses while keeping code testable and maintainable.

How This Skill Works

Controllers handle HTTP input and validation, then delegate to Services. Services perform business logic and enrich responses by fetching related data in parallel (e.g., metadata, scores, activity, location) via Promise.all, then assemble the final enriched object. Queries access the database and return lean data, enabling fast, reusable primitives.

When to Use It

  • Building REST APIs with complex data aggregation
  • GraphQL resolvers needing data from multiple sources
  • APIs whose responses combine data from multiple queries
  • Systems requiring testable, maintainable code
  • When you want performance through parallel data fetching

Quick Start

  1. Step 1: npx clawhub@latest install service-layer-architecture
  2. Step 2: Create Layer 1 Controllers, Layer 2 Services, and Layer 3 Queries as shown in the examples
  3. Step 3: Run the app and test endpoints; verify enriched responses and parallel fetch with Promise.all

Best Practices

  • Keep controllers thin; delegate validation and processing to the service layer
  • Use Promise.all to fetch related data in parallel in the service layer
  • Enrich data in the service layer and transform it before returning
  • Use lean() in queries to improve read performance
  • Document layer responsibilities and maintain tests across Controllers, Services, and Queries

Example Use Cases

  • REST API for a user profile that aggregates metadata, scores, activity, and location to present a rich profile.
  • GraphQL resolver that composes data from multiple microservices into a single EnrichedEntity.
  • Entity listing endpoint that returns sorted enriched entities based on total score.
  • Input-validated REST endpoint that returns 400/404 errors when IDs are invalid or missing.
  • Performance-optimized API using parallel data fetching to reduce latency.

Frequently Asked Questions

Add this skill to your agents
Sponsor this space

Reach thousands of developers