Install
openclaw skills install service-layer-architectureController-service-query layered API architecture with data enrichment and parallel fetching. Use when building REST APIs or GraphQL resolvers with clean separation of concerns. Triggers on API architecture, service layer, controller pattern, data enrichment, REST API.
openclaw skills install service-layer-architectureClean, performant API layers with proper separation of concerns and parallel data fetching.
┌─────────────────────────────────────────────────────┐
│ Controllers │ HTTP handling, validation │
├─────────────────────────────────────────────────────┤
│ Services │ Business logic, data enrichment │
├─────────────────────────────────────────────────────┤
│ Queries │ Database access, raw data fetch │
└─────────────────────────────────────────────────────┘
// 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;
});
// 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);
};
// 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();
};
// 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
| Task | Layer |
|---|---|
| Parse request params | Controller |
| Validate input | Controller |
| Set HTTP status | Controller |
| Combine multiple queries | Service |
| Transform data | Service |
| Sort/filter results | Service |
| Run database query | Query |