agentsclimarketplace

Rust architecture

Skill longzhi/rust-coding-standards/skills/rust-architecture

Use when creating modules, organizing project structure, designing layered architecture (handler/service/domain/repo), dependency injection, or DDD patterns in Rust.From its SKILL.md

Install
npx -y skills add longzhi/rust-coding-standards --skill rust-architecture

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 2 stars2 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

SKILL.md

9.1 KB, as published. Nobody here has run it

Rust Architecture Standards

Code Organization

src/
├── core/                  # Business semantic kernel (core types, no external deps)
│   ├── mod.rs
│   └── error.rs

├── infra/                 # Shared infrastructure (technical, no business semantics)
│   ├── mod.rs
│   ├── db.rs              # DB pool / transaction
│   ├── auth.rs            # JWT / auth
│   └── config.rs

├── shared/                # Cross-module shared (non-infrastructure)
│   ├── mod.rs
│   ├── utils.rs           # Utility functions (date, string, etc.)
│   └── types.rs           # Public types, shared DTOs

├── modules/               # Business modules (Bounded Contexts, vertical slices)
│   ├── mod.rs
│   ├── order/
│   │   ├── mod.rs
│   │   ├── domain.rs      # Domain layer: domain models + domain services
│   │   ├── service.rs     # Application layer: coordinates domain + repo + infra
│   │   ├── repo.rs        # Infrastructure layer: data access
│   │   ├── handler.rs     # Presentation layer: HTTP handlers
│   │   └── infra/         # Infrastructure layer: module-specific (as needed)
│   │       ├── mod.rs
│   │       └── payment_client.rs
│   ├── product/
│   └── user/

├── server/                # Assembly / startup
│   ├── mod.rs
│   ├── router.rs
│   └── state.rs

├── main.rs                # Executable entry point
└── lib.rs                 # Library entry, declares module structure, re-exports

Module declaration style: Using mod.rs is recommended (avoids confusion from directory + same-name .rs file coexisting).

Module internal structure (each module follows this):

  • mod.rs — module declarations
  • domain.rs — Domain layer: domain models (aggregates, entities, value objects) + domain services (pure business logic across aggregates)
  • service.rs — Application layer: application services, coordinates domain, repo, and infra; handles transactions, authorization, auditing
  • repo.rs — Infrastructure layer: database access, SQL explicitly visible
  • handler.rs — Presentation layer: HTTP handlers, parameter parsing, response formatting
  • dto.rs — Presentation layer: Request/Response DTOs (optional; simple modules can inline in handler.rs)
  • infra/ — Infrastructure layer: module-specific infrastructure (as needed, not required)

DTO placement rules:

  • Module-internal DTOs go in dto.rs or inline in handler.rs
  • Cross-module shared DTOs go in shared/types.rs
  • DTOs are pure data structures for transport; they contain no business logic

DDD layer mapping:

handler.rs   → Presentation Layer
service.rs   → Application Layer
domain.rs    → Domain Layer
repo.rs      → Infrastructure Layer
infra/       → Infrastructure Layer

Key distinction:

  • domain.rs is pure Rust: no external dependencies, no database awareness
  • service.rs is the coordination layer: calls domain for business logic, calls repo for persistence

Relationship between module-level repo.rs and infra/:

service.rs
    ├── calls repo.rs      (database operations)
    └── calls infra/       (caches, external services, etc.)
  • repo.rs and infra/ are peers — both called by service, neither depends on the other
  • repo stays at module root because:
    • In DDD, the repository has special status as the persistence boundary of an aggregate
    • repo changes frequently with business evolution; other infra is relatively stable
    • Keeping repo at module root makes the data access layer immediately visible
  • infra/ is added as needed — not every module requires it

Top-level directory responsibilities:

DirectoryResponsibilityExamples
core/Business semantics, core types (minimal external deps)Core error types
shared/Utility functions, public types, shared DTOsDate handling, validation
infra/Shared infrastructure (module-specific infra goes in module infra/)DB pool, Redis, Auth

lib.rs purpose:

  • Declares module structure (pub mod core; pub mod infra; pub mod modules;)
  • Re-exports commonly used types
  • Keeps main.rs concise
  • Enables tests to reference the library directly

Architecture Patterns

Design Principle: Pragmatic DDD

The architecture follows DDD (Domain-Driven Design) style but avoids rigid adherence. Core principles:

Adopted DDD concepts:

  • Domain model first: Understand business concepts before designing code structure
  • Bounded Contexts: Divide modules by business boundary (modules/), not by technical layer
  • Aggregates and Aggregate Roots: Core business objects (Order, Product, User) have clear consistency boundaries
  • Repository pattern: Repo encapsulates data access; Domain has no knowledge of SQL
  • Domain vs Application services: Pure cross-aggregate business logic lives in domain.rs (domain service); coordination logic lives in service.rs (application service)

Pragmatic trade-offs:

  • No pursuit of pure Domain Models: If adding a database column saves complex domain logic, add the column
  • No forced event-driven architecture: Synchronous calls are fine for MVP; introduce domain events later as needed
  • Uniform structure, simple code: Even simple CRUD maintains the full handler -> service -> domain -> repo structure; simple cases just have less code per layer
  • Anemic models are acceptable: DTOs, simple config types can be plain data structures

Decision criterion:

Ask yourself: What benefit does this abstraction provide?

If the answer is "it's more DDD-compliant" → don't do it
If the answer is "easier to test / easier to change / easier to understand" → do it

Anti-patterns:

  • Do not introduce an interface/trait with only one implementation just for "decoupling"
  • Do not pre-design abstractions for "extensibility" you don't currently need
  • Do not implement features that aren't required yet for "completeness"
  • Do not over-complicate simple problems (e.g., turning a single field into a separate table + join query)

Summary: DDD is a tool, not a goal. Keep the system flexible and extensible enough, without over-engineering.

Dependency Injection and State Management

AppState organization: Single struct.

// src/server/state.rs
pub struct AppState {
    pub db: PgPool,
    pub redis: RedisPool,
    pub config: AppConfig,
    // Services
    pub order_service: Arc<OrderService>,
    pub user_service: Arc<UserService>,
    pub auth_service: Arc<AuthService>,
    // ...
}

Service dependency rules: Enforce the Service layer.

  • Handlers must NOT call Repo directly; always go through Service
  • Cross-cutting concerns (authorization, audit logging) are handled uniformly in Service
  • Services may call each other, but avoid circular dependencies
// Handler → Service → Repo
async fn get_order(
    State(state): State<AppState>,
    Path(id): Path<Uuid>,
) -> Result<Json<Order>, ApiError> {
    let order = state.order_service.get_order(id).await?;
    Ok(Json(order))
}

Config Access Rules

Direct environment variable access is prohibited: Business code must not call std::env::var() directly. All configuration must go through the Config system.

// Bad: reading env vars directly
let api_key = std::env::var("OPENAI_API_KEY").unwrap();

// Good: reading through Config
let api_key = config.openai.api_key.clone();

Environment variable to Config mapping:

// src/infra/config.rs
#[derive(Debug, Clone, Deserialize)]
pub struct SomeConfig {
    #[serde(default = "default_some_value")]
    pub some_value: String,
}

fn default_some_value() -> String {
    std::env::var("SOME_ENV_VAR").unwrap_or_default()
}

Rationale:

  • Centralized configuration management for easier tracking and debugging
  • Supports multiple sources (TOML files, env vars, CLI args) with unified merging
  • Type-safe with startup-time validation
  • Enables mock config injection in tests

Performance Guidelines

Database Query Principles

  • Avoid N+1: Use JOINs or batch queries for associated data; never query inside a loop
  • Index wisely: Index columns used in WHERE/ORDER BY; mind column order in composite indexes
  • Pagination is mandatory: List queries must be paginated; never return unbounded results
  • EXPLAIN complex queries: Check execution plans for multi-table JOINs or subqueries

Caching Strategy

Use the Cache-Aside pattern as the default caching approach: check cache first, on miss query the database, then populate the cache. Choose cache location (Redis for shared/distributed state, in-process memory for read-heavy config data) and TTL based on data volatility and consistency requirements. Always implement explicit cache invalidation when the source data is mutated.

Keep looking

Skills are one crate of 627,801. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.