Skip to content

MairwunNx/Feature-Action-Architecture

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

12 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

πŸ• Feature-Action Architecture (FAA)

Slice your backend into features. Replace services with actions. Keep it clean.

FAA is a backend adaptation of Feature-Sliced Design β€” an architecture where code is organized by business domain (vertical slices), and business logic lives in isolated action functions instead of monolithic service classes.

It's not tied to any language or paradigm. Use FP, OOP, or whatever works for you. All examples here are in TypeScript + functional style, but the ideas are universal.

πŸ‡·πŸ‡Ί Π§ΠΈΡ‚Π°Ρ‚ΡŒ Π½Π° русском


πŸ“– Table of Contents


😩 The Problem

Traditional backend architecture gives you horizontal layers:

src/
β”œβ”€β”€ controllers/    # Thin wrappers that call services
β”œβ”€β”€ services/       # god objects that do everything
β”œβ”€β”€ repositories/   # Data access, often duplicating service logic
└── models/         # Schemas, far away from the code that uses them

As the project grows:

  • UserService becomes a 500-line monster handling auth, profiles, settings, notifications...
  • Repositories duplicate service logic or become pass-through wrappers
  • Adding a feature means touching 4+ directories
  • Nobody knows whether a query belongs in a service or repository

Sound familiar?


πŸ€” What is FAA?

FAA flips the traditional approach:

Traditional FAA
Organize by technical role (controllers, services...) Organize by business domain (auth, leaderboard...)
UserService class with 20 methods loginAction, getProfileAction β€” one function, one job
Repositories with ambiguous boundaries Data access lives where it's used
Implicit dependencies via imports Explicit dependencies via factories + DI

🎯 Core Principles

# Principle TL;DR
1 πŸ• Slice, don't layer No global services/, controllers/, repositories/. Organize by feature.
2 ⚑ Actions over Services No UserService class. Write loginAction β€” one function, one job.
3 ⬇️ Strict downward flow App β†’ Features β†’ Entities β†’ Shared. Never up. Never sideways.
4 πŸ“ Localize data access Generic CRUD β†’ Entities. Complex queries β†’ the Feature that needs them.
5 πŸ”Œ Explicit dependencies Factory functions + DI. No hidden globals.

πŸ—οΈ Layer Hierarchy

graph TD
    APP["πŸ—οΈ App<br/>Assembly Β· DI Β· Routing"] --> FEATURES
    FEATURES["⚑ Features<br/>Business Scenarios"] --> ENTITIES
    ENTITIES["πŸ“¦ Entities<br/>Domain Data Β· CRUD"] --> SHARED
    SHARED["πŸ”§ Shared<br/>Infra Β· Utilities"]

    style APP fill:#e1f5fe,stroke:#0288d1
    style FEATURES fill:#f3e5f5,stroke:#7b1fa2
    style ENTITIES fill:#e8f5e9,stroke:#388e3c
    style SHARED fill:#fff3e0,stroke:#f57c00
Loading
Layer What it does What it contains
πŸ—οΈ App Assembles everything Server init, DI container, router, middleware wiring (auth guards, rate limiting, etc.)
⚑ Features Implements use cases Actions, HTTP handlers, feature-specific queries
πŸ“¦ Entities Owns domain data DB models, CRUD (DAL), reusable domain logic
πŸ”§ Shared Provides tools Logger, DB drivers, config, HTTP utils, pure helpers, reusable middleware

Important

Each layer can only import from layers below it. Never up, never sideways.


πŸ“ Project Structure

src/
β”œβ”€β”€ app/                          # πŸ—οΈ Assembly
β”‚   β”œβ”€β”€ container.ts              # DI wiring
β”‚   β”œβ”€β”€ routes.ts                 # Registers routes from features
β”‚   └── server.ts                 # Server init & lifecycle
β”‚
β”œβ”€β”€ features/                     # ⚑ Business Scenarios
β”‚   β”œβ”€β”€ auth/
β”‚   β”‚   β”œβ”€β”€ api/handler.ts        # HTTP layer
β”‚   β”‚   β”œβ”€β”€ login.action.ts       # Business logic (THE action)
β”‚   β”‚   β”œβ”€β”€ lib/                  # Feature-local helpers
β”‚   β”‚   β”œβ”€β”€ types.ts
β”‚   β”‚   └── index.ts              # Public API
β”‚   β”‚
β”‚   β”œβ”€β”€ leaderboard/
β”‚   β”‚   β”œβ”€β”€ race/                 # Sub-feature (independent!)
β”‚   β”‚   β”‚   β”œβ”€β”€ api/handler.ts
β”‚   β”‚   β”‚   β”œβ”€β”€ db/pipelines.ts   # Feature-specific queries
β”‚   β”‚   β”‚   β”œβ”€β”€ get-race.action.ts
β”‚   β”‚   β”‚   └── index.ts
β”‚   β”‚   └── ladder/
β”‚   β”‚       └── ...
β”‚   └── ...
β”‚
β”œβ”€β”€ entities/                     # πŸ“¦ Domain Data
β”‚   β”œβ”€β”€ user/
β”‚   β”‚   β”œβ”€β”€ model.ts              # DB schema
β”‚   β”‚   β”œβ”€β”€ dal.ts                # Generic CRUD
β”‚   β”‚   β”œβ”€β”€ lib/                  # Reusable domain logic
β”‚   β”‚   β”‚   β”œβ”€β”€ queries.ts        # Complex reads (getOrCreate)
β”‚   β”‚   β”‚   β”œβ”€β”€ commands.ts       # Complex writes (updatePrivacy)
β”‚   β”‚   β”‚   └── helpers.ts        # Pure functions (normalizeName)
β”‚   β”‚   └── types.ts
β”‚   └── ...
β”‚
└── shared/                       # πŸ”§ Infrastructure
    β”œβ”€β”€ api/                      # HTTP primitives (errors, responses, middleware)
    β”œβ”€β”€ lib/                      # Pure functions (datetime, encoding)
    └── infra/                    # Drivers (DB, logger, config)

Tip

Feature groups like leaderboard/ are just folders for organization. Each subfolder (race/, ladder/) is an independent feature β€” no cross-imports allowed.


⚑ Quick Example

The Flow

sequenceDiagram
    participant C as Client
    participant H as Handler
    participant A as Action
    participant E as Entity DAL
    participant DB as Database

    C->>H: POST /api/auth/login
    H->>A: loginAction(data)
    A->>E: userDal.findById(id)
    E->>DB: SELECT ...
    DB-->>E: User row
    E-->>A: User object
    A-->>H: { token, user }
    H-->>C: 200 { data: { token, user } }
Loading
πŸ“¦ Entity β€” User DAL
// entities/user/dal.ts
import { UserModel } from "./model";

export const createUserDal = () => ({
  findById: (id: number) =>
    UserModel.findOne({ user_id: id }).lean(),

  create: (userId: number, username?: string) =>
    UserModel.create({ user_id: userId, username }),
});
⚑ Feature β€” Login Action
// features/auth/login.action.ts
type Deps = {
  userDal: ReturnType<typeof createUserDal>;
  config: AppConfig;
};

export const createLoginAction = (deps: Deps) =>
  async (telegramData: TelegramAuth) => {
    let user = await deps.userDal.findById(telegramData.id);
    if (!user) {
      user = await deps.userDal.create(telegramData.id, telegramData.username);
    }
    const token = signToken(user, deps.config.secret);
    return { token, user };
  };

createLoginAction.inject = ["userDal", "config"] as const;
🌐 Feature β€” HTTP Handler
// features/auth/api/handler.ts
export const createLoginHandler = (login: ReturnType<typeof createLoginAction>) =>
  async (req: Request) => {
    const body = await req.json();
    const result = await login(body);
    return Response.json({ data: result });
  };

createLoginHandler.inject = ["loginAction"] as const;
πŸ—οΈ App β€” Wiring it all together (for example, typed-inject)
// app/container.ts
import { createInjector } from "typed-inject";

export const createContainer = () => {
  const config = loadConfig();

  return createInjector()
    .provideValue("config", config)
    .provideFactory("userDal", createUserDal)
    .provideFactory("loginAction", createLoginAction)
    .provideFactory("loginHandler", createLoginHandler);
};

const loginHandler = createContainer().resolve("loginHandler");

🚦 Dependency Rules

graph LR
    F1["Feature A"] -.-x|"❌"| F2["Feature B"]
    E1["Entity A"] -.-x|"❌"| E2["Entity B"]
    FE["Feature"] -->|"βœ…"| EN["Entity"]
    EN2["Entity"] -.-x|"❌"| FE2["Feature"]

    style F1 fill:#ffcdd2,stroke:#c62828
    style F2 fill:#ffcdd2,stroke:#c62828
    style E1 fill:#ffcdd2,stroke:#c62828
    style E2 fill:#ffcdd2,stroke:#c62828
    style FE fill:#c8e6c9,stroke:#2e7d32
    style EN fill:#c8e6c9,stroke:#2e7d32
Loading
Direction Verdict Example
Feature β†’ Entity βœ… Allowed login.action.ts imports userDal
Feature β†’ Shared βœ… Allowed Action imports datetime utility
Feature β†’ Feature ❌ Forbidden Everything: code, types, errors. Push shared logic down to Entity
Entity β†’ Entity ❌ Forbidden Entities are isolated
Entity β†’ Feature ❌ Forbidden Never import upward
Shared β†’ anything above ❌ Forbidden Shared is the foundation

Warning

If two features need the same logic β€” don't import horizontally. Move the shared logic down to an Entity or Shared layer.


πŸ”— Connect FAA to Your Project

You have two ways to attach FAA documentation to your repo (for humans and AI agents):

1) Link to this repository

Add a link in your project prompt/README (https://github.com/MairwunNx/Feature-Action-Architecture/blob/master/MANIFEST.md).

This is quick, but not always effective β€” many LLM agents fetch links partially or ignore them depending on their tooling. That can lead to an incomplete picture.

2) Add FAA as a git submodule (recommended)

This makes the docs local to the repo, so agents can always read them.

Git submodule declaration (example):

# .gitmodules
[submodule "Feature-Action-Architecture"]
  path = Feature-Action-Architecture
  url = https://github.com/MairwunNx/Feature-Action-Architecture.git

or

git submodule add https://github.com/MairwunNx/Feature-Action-Architecture.git Feature-Action-Architecture

Project prompt snippet (you can paste into your AI/project prompt):

### Project architecture

All FAA (Feature-Action Architecture) docs live in `./Feature-Action-Architecture`.
Use the example closest to our stack.

Stack: <your stack here> # example: TS & Bun & typed-inject
Example: `Feature-Action-Architecture/examples/ts-bun.md`  # replace with your file

AI notes: `Feature-Action-Architecture/AI.md`
Architecture Manifest: `Feature-Action-Architecture/MANIFEST.md`

πŸ“š Learn More

Document What's inside
πŸ“œ MANIFEST.md The philosophy, the "why", decision guide
πŸ€– AI.md Rules & patterns for AI/LLM agents working with FAA

Language Examples

Full working examples with project structure, DI wiring, and code snippets:

Stack Example
TypeScript + Bun examples/ts-bun.md
Kotlin + Spring Boot examples/kotlin-springboot.md
Go + Gin + uber-fx examples/golang-gin.md
Python + Django examples/python-django.md
C# + ASP.NET Core examples/csharp-asp.md
Java + Spring Boot examples/java-springboot.md
PHP + Laravel examples/php-laravel.md
F# + Giraffe examples/fsharp-giraffe.md
Rust + Axum examples/rust-axum.md

Note

FAA is language-agnostic and paradigm-agnostic. FP, OOP, whatever β€” the principles apply. Examples here use TypeScript + functional style because that's what we like πŸ•

About

πŸ• Feature-Action Architecture β€” Backend architecture for scalable and maintainable systems. Explicit dependencies, clear rules, no spaghetti code.

Topics

Resources

Stars

Watchers

Forks

Contributors