Skills

layered-architecture

layered-architecture
npx @loomcraft/cli add skill layered-architecture
Frontmatter

Name

layered-architecture

Description

Enforces strict layered architecture: Presentation → Facade → Service → DAL → Persistence. Use when structuring a Next.js application, creating new features with separation of concerns, or refactoring code into layers.

Content
# Layered Architecture

Strict separation of concerns across five layers. Each layer only calls the layer directly below it.

## Critical Rules

- **Never skip layers** — presentation must not call services directly.
- **No business logic in facades** — they coordinate, not decide.
- **No auth checks in DAL** — DAL is purely data access.
- **No database calls in services** — services call DAL.
- **Functional style** — pure functions, no classes, prefer composition over inheritance.
- **Top-down design** — keep functions short, extract when >100 lines.

## Layer Overview

```
Presentation (RSC / Client Components)
    ↓
Facade (entry point for business logic)
    ↓
Service (business rules, authorization)
    ↓
DAL — Data Access Layer (queries, data shaping)
    ↓
Persistence (Drizzle ORM / database)
```

## Presentation Layer

- React Server Components and Client Components live here.
- Components call **facades only** — never services or DAL directly.
- RSC by default. Only add `"use client"` when strictly needed (hooks, events, browser APIs).
- Top-down design: if a component exceeds ~100 lines, extract sub-functions/sub-components.
- Functional over OOP — pure functions, composition, immutability.

```tsx
// app/(app)/users/page.tsx — Presentation
import { getUserList } from "@/facades/user.facade";

export default async function UsersPage() {
  const users = await getUserList();
  return <UserTable users={users} />;
}
```

## Facade Layer

- Entry points for business logic. One facade per domain entity.
- Located in `src/facades/` — files named `{entity}.facade.ts`.
- Facades orchestrate service calls and transform data for the presentation.
- Facades handle auth context extraction and pass it down.
- Never contain business logic — only coordination.

```ts
// src/facades/user.facade.ts
import { getCurrentUser } from "@/lib/auth";
import { listUsers, createUser } from "@/services/user.service";

export async function getUserList() {
  const currentUser = await getCurrentUser();
  return listUsers(currentUser);
}
```

## Service Layer

- Contains all business rules and authorization checks.
- Located in `src/services/` — files named `{entity}.service.ts`.
- Services receive the auth context as parameter — never fetch it themselves.
- CASL authorization checks happen here.
- Services call DAL functions for data access.

```ts
// src/services/user.service.ts
import { ForbiddenError } from "@casl/ability";
import { defineAbilityFor } from "@/lib/casl";
import { findAllUsers } from "@/dal/user.dal";

export async function listUsers(currentUser: AuthUser) {
  const ability = defineAbilityFor(currentUser);
  ForbiddenError.from(ability).throwUnlessCan("read", "User");
  return findAllUsers();
}
```

## Data Access Layer (DAL)

- Pure data access — no business logic, no authorization.
- Located in `src/dal/` — files named `{entity}.dal.ts`.
- DAL functions shape data: select specific columns, join relations, paginate.
- Always return typed results — never raw query results.

```ts
// src/dal/user.dal.ts
import { db } from "@/lib/db";
import { users } from "@/schema";

export async function findAllUsers() {
  return db.select({
    id: users.id,
    name: users.name,
    email: users.email,
    role: users.role,
  }).from(users);
}
```

## Persistence Layer

- Drizzle ORM schema definitions and database client.
- Located in `src/schema/` for table definitions, `src/lib/db.ts` for the client.
- Schema-only — no query logic here.

## File Naming Convention

```
src/
  facades/         # {entity}.facade.ts
  services/        # {entity}.service.ts
  dal/             # {entity}.dal.ts
  schema/          # {entity}.ts (Drizzle table defs)
  lib/db.ts        # Database client
```

- All new files use **kebab-case**.
- One file per entity per layer — avoid catch-all files.

## Do

- Call only the layer directly below -- Presentation calls Facade, Facade calls Service, Service calls DAL.
- Pass the auth context (current user) as a parameter from Facade to Service -- never fetch it inside a service.
- Keep facades thin -- they orchestrate calls and shape data for the UI, nothing more.
- Put all authorization checks (CASL) in the Service layer.
- Put all data shaping (column selection, joins, pagination) in the DAL layer.
- Create one file per entity per layer: `user.facade.ts`, `user.service.ts`, `user.dal.ts`.
- Use functional style -- pure functions, no classes, prefer composition.
- Extract sub-functions when any function exceeds ~100 lines.

## Don't

- Don't call a DAL function directly from a component or page -- always go through Facade then Service.
- Don't put business rules or authorization in the Facade layer -- that belongs in Service.
- Don't put auth checks or business logic in the DAL -- it is purely data access.
- Don't import Drizzle `db` in service or facade files -- only DAL and `lib/db.ts` touch the database.
- Don't create catch-all files like `api.service.ts` that mix multiple entities.
- Don't let the persistence layer (schema files) contain query logic.
- Don't pass raw Drizzle query results up through the layers -- shape them in the DAL.

## Anti-Patterns

| Anti-Pattern | Problem | Fix |
|---|---|---|
| **Layer skipping** | A component imports a service or DAL function directly, bypassing the facade. | Always route through Facade -- it ensures auth context is attached and coordination is centralized. |
| **Fat facade** | Facade contains business rules, conditionals, or authorization logic. | Move business logic to the Service layer; the Facade only orchestrates and transforms. |
| **Auth in DAL** | DAL functions check user permissions before running queries. | Remove auth from DAL; let the Service layer handle all authorization with CASL. |
| **Database in Service** | Service imports `db` and runs queries directly instead of calling DAL functions. | Extract every query into the DAL; the Service layer never touches Drizzle directly. |
| **God file** | A single `data.ts` or `api.ts` that handles users, posts, and orgs in one file. | Split into one file per entity per layer: `user.dal.ts`, `post.dal.ts`, etc. |
| **Upward dependency** | A DAL file imports from a service or facade, creating a circular dependency. | Dependencies flow strictly downward: Presentation > Facade > Service > DAL > Persistence. |
Files

No additional files