Nghi-NV

rust-backend

0
0
# Install this skill:
npx skills add Nghi-NV/create-agent-skills --skill "rust-backend"

Install specific skill from multi-skill repository

# Description

Build robust backend services with Rust using Clean Architecture. Use when creating APIs, microservices, or server applications with domain-driven design, repository pattern, and async operations.

# SKILL.md


name: rust-backend
description: Build robust backend services with Rust using Clean Architecture. Use when creating APIs, microservices, or server applications with domain-driven design, repository pattern, and async operations.


Rust Backend Development

Build production-ready backend services with Rust following Clean Architecture principles: Domain-Driven Design, Repository Pattern, and layered separation of concerns.

When to Use This Skill

  • Building REST APIs or gRPC services with Rust
  • Creating microservices with clean architecture
  • Implementing domain-driven design patterns
  • Setting up database layers with SeaORM
  • Writing async services with proper error handling

Examples & Resources

Examples

Resources

Architecture Overview

project/
β”œβ”€β”€ Cargo.toml              # Workspace root
β”œβ”€β”€ bin/                    # Binaries (API servers, CLI tools)
β”‚   └── api-server/
β”‚       β”œβ”€β”€ src/
β”‚       β”‚   β”œβ”€β”€ main.rs     # Entry point
β”‚       β”‚   β”œβ”€β”€ lib.rs      # Server setup
β”‚       β”‚   β”œβ”€β”€ api/        # HTTP handlers
β”‚       β”‚   └── cmd/        # CLI arguments
└── lib/                    # Libraries (business logic)
    β”œβ”€β”€ domain/             # Entities, value objects, errors
    β”œβ”€β”€ services/           # Business logic, repository traits
    └── database/           # Repository implementations

Layer Responsibilities

Layer Responsibility Dependencies
bin/ Entry points, server setup services, database
lib/domain Entities, value objects, errors None (pure Rust)
lib/services Business logic, repository traits domain
lib/database Repository implementations domain, services

[!IMPORTANT]
Dependencies flow inward: bin β†’ services β†’ domain. Domain layer has NO external dependencies.

Quick Start

1. Entity Pattern

// lib/domain/src/entities/user.rs
#[derive(Debug, Clone, PartialEq)]
pub struct User {
    id: UserId,
    email: String,
    name: Option<String>,
    is_active: bool,
}

impl User {
    pub fn new(id: UserId, email: impl Into<String>, name: Option<String>) -> Result<Self, DomainError> {
        let email = email.into().trim().to_lowercase();
        if email.is_empty() || !email.contains('@') {
            return Err(DomainError::validation("invalid email"));
        }
        Ok(Self { id, email, name, is_active: true })
    }

    pub fn create(email: impl Into<String>, name: Option<String>) -> Result<Self, DomainError> {
        Self::new(UserId::new(), email, name)
    }

    // Getters
    pub fn id(&self) -> UserId { self.id }
    pub fn email(&self) -> &str { &self.email }

    // Mutations
    pub fn deactivate(&mut self) { self.is_active = false; }
}

2. Repository Trait Pattern

// lib/services/src/users.rs
#[async_trait]
pub trait UserRepository: Send + Sync {
    async fn fetch(&self, id: UserId) -> Result<Option<User>, RepositoryError>;
    async fn list(&self, filter: &UserFilter) -> Result<Vec<User>, RepositoryError>;
    async fn save(&self, user: &User) -> Result<(), RepositoryError>;
    async fn delete(&self, id: UserId) -> Result<(), RepositoryError>;
}

// Blanket impl for Arc<R>
#[async_trait]
impl<R: UserRepository> UserRepository for Arc<R> {
    async fn fetch(&self, id: UserId) -> Result<Option<User>, RepositoryError> {
        (**self).fetch(id).await
    }
    // ... other methods
}

3. Service Pattern

// lib/services/src/users.rs
#[derive(Clone)]
pub struct UserService<R: UserRepository> {
    repository: R,
}

impl<R: UserRepository> UserService<R> {
    pub fn new(repository: R) -> Self { Self { repository } }

    pub async fn create_user(&self, new: NewUser) -> Result<User, UserServiceError> {
        let user = User::create(new.email, new.name)?;
        self.repository.save(&user).await?;
        Ok(user)
    }

    pub async fn get_user(&self, id: UserId) -> Result<User, UserServiceError> {
        self.repository.fetch(id).await?
            .ok_or(UserServiceError::NotFound { id })
    }
}

4. Repository Implementation

// lib/database/src/repositories/users.rs
#[derive(Clone)]
pub struct UserSeaOrmRepository {
    conn: DatabaseConnection,
}

#[async_trait]
impl UserRepository for UserSeaOrmRepository {
    async fn fetch(&self, id: UserId) -> Result<Option<User>, RepositoryError> {
        let model = user::Entity::find_by_id(id.into())
            .one(&self.conn)
            .await
            .map_err(RepositoryError::new)?;

        model.map(|m| build_user(m)).transpose()
    }

    async fn save(&self, user: &User) -> Result<(), RepositoryError> {
        // Upsert logic - see examples/database-layer.md
    }
}

5. API Handler

// bin/api-server/src/api/users.rs
#[OpenApi]
impl<R: UserRepository + Clone + Send + Sync + 'static> UserController<R> {
    #[oai(path = "/users", method = "post")]
    async fn create_user(&self, body: Json<CreateUserRequest>) -> PoemResult<Json<UserResponse>> {
        let user = self.service.create_user(NewUser {
            email: body.0.email,
            name: body.0.name,
        }).await.map_err(map_error)?;

        Ok(Json(user.into()))
    }
}

Error Handling

// Domain errors
#[derive(Debug, Error, Clone, PartialEq)]
pub enum DomainError {
    #[error("validation error: {0}")]
    Validation(String),
    #[error("not found: {0}")]
    NotFound(String),
}

// Service errors
#[derive(Debug, Error)]
pub enum UserServiceError {
    #[error(transparent)]
    Domain(#[from] DomainError),
    #[error("user {id} not found")]
    NotFound { id: UserId },
    #[error("repository error: {0}")]
    Repository(#[from] RepositoryError),
}

// Map to HTTP status
fn map_error(err: UserServiceError) -> PoemError {
    match err {
        UserServiceError::Domain(e) => PoemError::from_string(e.to_string(), StatusCode::BAD_REQUEST),
        UserServiceError::NotFound { .. } => PoemError::from_status(StatusCode::NOT_FOUND),
        UserServiceError::Repository(e) => PoemError::from_string(e.to_string(), StatusCode::INTERNAL_SERVER_ERROR),
    }
}

Testing Pattern

#[cfg(test)]
mod tests {
    // In-memory repository for testing
    #[derive(Default)]
    struct InMemoryUserRepository {
        users: Mutex<HashMap<UserId, User>>,
    }

    #[async_trait]
    impl UserRepository for InMemoryUserRepository {
        async fn fetch(&self, id: UserId) -> Result<Option<User>, RepositoryError> {
            Ok(self.users.lock().await.get(&id).cloned())
        }
        // ... other methods
    }

    #[tokio::test]
    async fn create_and_fetch_user() {
        let repo = Arc::new(InMemoryUserRepository::default());
        let service = UserService::new(repo);

        let user = service.create_user(NewUser { 
            email: "[email protected]".into(), 
            name: None 
        }).await.unwrap();

        let fetched = service.get_user(user.id()).await.unwrap();
        assert_eq!(fetched.email(), "[email protected]");
    }
}

Best Practices

Practice Description
Domain isolation Domain layer has NO external dependencies
Repository trait Define in services, implement in database
Error hierarchy Domain β†’ Service β†’ API error mapping
Validate in new() Validate at entity construction time
Arc for sharing Use Arc<R> blanket impl for shared repositories
In-memory tests Use HashMap-based repo for fast unit tests

Key Dependencies

[workspace.dependencies]
tokio = { version = "1", features = ["full"] }
async-trait = "0.1"
poem = "3.1"
poem-openapi = { version = "5.1", features = ["swagger-ui"] }
sea-orm = { version = "1.1", features = ["sqlx-postgres", "runtime-tokio-rustls"] }
thiserror = "2.0"
anyhow = "1.0"
uuid = { version = "1.11", features = ["v4", "serde"] }
chrono = { version = "0.4", features = ["serde"] }
serde = { version = "1.0", features = ["derive"] }

See resources/cargo-template.toml for full template.

References

# Supported AI Coding Agents

This skill is compatible with the SKILL.md standard and works with all major AI coding agents:

Learn more about the SKILL.md standard and how to use these skills with your preferred AI coding agent.