Initial commit
This commit is contained in:
346
commands/rust-hex-init.md
Normal file
346
commands/rust-hex-init.md
Normal file
@@ -0,0 +1,346 @@
|
||||
---
|
||||
description: Initialize a hexagonal architecture project structure for Rust
|
||||
---
|
||||
|
||||
You are helping initialize a Rust project with hexagonal architecture (ports and adapters pattern).
|
||||
|
||||
## Your Task
|
||||
|
||||
Create a complete hexagonal architecture directory structure with example files to help the user get started.
|
||||
|
||||
## Steps
|
||||
|
||||
1. **Verify or Create Base Directory Structure**
|
||||
|
||||
Create the following structure:
|
||||
```
|
||||
src/
|
||||
├── domain/
|
||||
│ ├── mod.rs
|
||||
│ ├── models.rs
|
||||
│ └── services.rs
|
||||
├── ports/
|
||||
│ ├── mod.rs
|
||||
│ ├── driving.rs
|
||||
│ └── driven.rs
|
||||
├── adapters/
|
||||
│ ├── mod.rs
|
||||
│ ├── driving/
|
||||
│ │ └── mod.rs
|
||||
│ └── driven/
|
||||
│ └── mod.rs
|
||||
└── lib.rs (or main.rs if it exists)
|
||||
```
|
||||
|
||||
2. **Create Domain Layer Files**
|
||||
|
||||
**src/domain/mod.rs**:
|
||||
```rust
|
||||
//! Domain layer - Core business logic
|
||||
//!
|
||||
//! This layer contains:
|
||||
//! - Domain models (entities, value objects)
|
||||
//! - Business rules and validations
|
||||
//! - Domain services
|
||||
//!
|
||||
//! The domain layer has NO dependencies on ports or adapters.
|
||||
|
||||
pub mod models;
|
||||
pub mod services;
|
||||
```
|
||||
|
||||
**src/domain/models.rs**:
|
||||
```rust
|
||||
//! Domain models and entities
|
||||
//!
|
||||
//! Define your business entities here with their behaviors.
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// Example domain entity
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ExampleEntity {
|
||||
id: String,
|
||||
name: String,
|
||||
}
|
||||
|
||||
impl ExampleEntity {
|
||||
pub fn new(id: String, name: String) -> Result<Self, ValidationError> {
|
||||
if name.is_empty() {
|
||||
return Err(ValidationError::EmptyName);
|
||||
}
|
||||
Ok(Self { id, name })
|
||||
}
|
||||
|
||||
pub fn id(&self) -> &str {
|
||||
&self.id
|
||||
}
|
||||
|
||||
pub fn name(&self) -> &str {
|
||||
&self.name
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum ValidationError {
|
||||
#[error("Name cannot be empty")]
|
||||
EmptyName,
|
||||
}
|
||||
```
|
||||
|
||||
**src/domain/services.rs**:
|
||||
```rust
|
||||
//! Domain services - Business logic orchestration
|
||||
//!
|
||||
//! Domain services coordinate between entities and use ports
|
||||
//! for external dependencies.
|
||||
|
||||
use super::models::{ExampleEntity, ValidationError};
|
||||
use crate::ports::driven::ExampleRepository;
|
||||
|
||||
/// Example domain service
|
||||
pub struct ExampleService<R>
|
||||
where
|
||||
R: ExampleRepository,
|
||||
{
|
||||
repository: R,
|
||||
}
|
||||
|
||||
impl<R> ExampleService<R>
|
||||
where
|
||||
R: ExampleRepository,
|
||||
{
|
||||
pub fn new(repository: R) -> Self {
|
||||
Self { repository }
|
||||
}
|
||||
|
||||
pub async fn get_entity(&self, id: &str) -> Result<ExampleEntity, ServiceError> {
|
||||
self.repository
|
||||
.find_by_id(id)
|
||||
.await
|
||||
.map_err(ServiceError::Repository)
|
||||
}
|
||||
|
||||
pub async fn create_entity(&self, name: String) -> Result<ExampleEntity, ServiceError> {
|
||||
let entity = ExampleEntity::new(uuid::Uuid::new_v4().to_string(), name)
|
||||
.map_err(ServiceError::Validation)?;
|
||||
|
||||
self.repository
|
||||
.save(&entity)
|
||||
.await
|
||||
.map_err(ServiceError::Repository)?;
|
||||
|
||||
Ok(entity)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum ServiceError {
|
||||
#[error("Validation error: {0}")]
|
||||
Validation(#[from] ValidationError),
|
||||
|
||||
#[error("Repository error: {0}")]
|
||||
Repository(#[from] crate::ports::driven::RepositoryError),
|
||||
}
|
||||
```
|
||||
|
||||
3. **Create Ports Layer Files**
|
||||
|
||||
**src/ports/mod.rs**:
|
||||
```rust
|
||||
//! Ports layer - Interfaces for adapters
|
||||
//!
|
||||
//! Ports define the contracts between the domain and the outside world:
|
||||
//! - Driving ports: What the domain offers to the outside
|
||||
//! - Driven ports: What the domain needs from the outside
|
||||
|
||||
pub mod driving;
|
||||
pub mod driven;
|
||||
```
|
||||
|
||||
**src/ports/driving.rs**:
|
||||
```rust
|
||||
//! Driving ports (Primary ports)
|
||||
//!
|
||||
//! These are the interfaces that the domain exposes to the outside world.
|
||||
//! Driving adapters (like REST APIs, CLI) will use these interfaces.
|
||||
|
||||
use crate::domain::models::ExampleEntity;
|
||||
use async_trait::async_trait;
|
||||
|
||||
/// Example driving port - what the application offers
|
||||
#[async_trait]
|
||||
pub trait ExampleUseCase: Send + Sync {
|
||||
async fn execute(&self, input: UseCaseInput) -> Result<ExampleEntity, UseCaseError>;
|
||||
}
|
||||
|
||||
pub struct UseCaseInput {
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum UseCaseError {
|
||||
#[error("Invalid input: {0}")]
|
||||
InvalidInput(String),
|
||||
|
||||
#[error("Service error: {0}")]
|
||||
Service(#[from] crate::domain::services::ServiceError),
|
||||
}
|
||||
```
|
||||
|
||||
**src/ports/driven.rs**:
|
||||
```rust
|
||||
//! Driven ports (Secondary ports)
|
||||
//!
|
||||
//! These are the interfaces that the domain needs from the outside world.
|
||||
//! Driven adapters (like database repositories, HTTP clients) implement these.
|
||||
|
||||
use crate::domain::models::ExampleEntity;
|
||||
use async_trait::async_trait;
|
||||
|
||||
/// Example repository port - what the domain needs
|
||||
#[async_trait]
|
||||
pub trait ExampleRepository: Send + Sync {
|
||||
async fn find_by_id(&self, id: &str) -> Result<ExampleEntity, RepositoryError>;
|
||||
async fn save(&self, entity: &ExampleEntity) -> Result<(), RepositoryError>;
|
||||
async fn delete(&self, id: &str) -> Result<(), RepositoryError>;
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum RepositoryError {
|
||||
#[error("Not found: {0}")]
|
||||
NotFound(String),
|
||||
|
||||
#[error("Database error: {0}")]
|
||||
Database(String),
|
||||
|
||||
#[error("Unknown error: {0}")]
|
||||
Unknown(String),
|
||||
}
|
||||
```
|
||||
|
||||
4. **Create Adapters Layer Files**
|
||||
|
||||
**src/adapters/mod.rs**:
|
||||
```rust
|
||||
//! Adapters layer - Implementations of ports
|
||||
//!
|
||||
//! Adapters connect the domain to the outside world:
|
||||
//! - Driving adapters: REST API, CLI, gRPC
|
||||
//! - Driven adapters: Database, HTTP clients, file systems
|
||||
|
||||
pub mod driving;
|
||||
pub mod driven;
|
||||
```
|
||||
|
||||
**src/adapters/driving/mod.rs**:
|
||||
```rust
|
||||
//! Driving adapters
|
||||
//!
|
||||
//! These adapters expose the application to the outside world.
|
||||
//! Examples: REST API, CLI, gRPC server, GraphQL
|
||||
|
||||
// Example: REST API adapter would go here
|
||||
// pub mod rest_api;
|
||||
```
|
||||
|
||||
**src/adapters/driven/mod.rs**:
|
||||
```rust
|
||||
//! Driven adapters
|
||||
//!
|
||||
//! These adapters implement the ports needed by the domain.
|
||||
//! Examples: PostgreSQL repository, HTTP client, Redis cache
|
||||
|
||||
// Example adapter implementations would go here
|
||||
// pub mod postgres_repository;
|
||||
// pub mod http_client;
|
||||
```
|
||||
|
||||
5. **Update lib.rs or main.rs**
|
||||
|
||||
Add to the top of `src/lib.rs` (or `src/main.rs` if no lib.rs exists):
|
||||
```rust
|
||||
//! Hexagonal Architecture Application
|
||||
//!
|
||||
//! This application follows the hexagonal architecture pattern:
|
||||
//! - Domain: Core business logic
|
||||
//! - Ports: Interfaces (traits)
|
||||
//! - Adapters: Implementations
|
||||
|
||||
pub mod domain;
|
||||
pub mod ports;
|
||||
pub mod adapters;
|
||||
```
|
||||
|
||||
6. **Update Cargo.toml**
|
||||
|
||||
Ensure the following dependencies are in Cargo.toml:
|
||||
```toml
|
||||
[dependencies]
|
||||
# Async runtime
|
||||
tokio = { version = "1", features = ["full"] }
|
||||
async-trait = "0.1"
|
||||
|
||||
# Error handling
|
||||
thiserror = "1.0"
|
||||
anyhow = "1.0"
|
||||
|
||||
# Serialization
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
|
||||
# UUID generation
|
||||
uuid = { version = "1.0", features = ["v4", "serde"] }
|
||||
|
||||
# Example: Database (uncomment if needed)
|
||||
# sqlx = { version = "0.7", features = ["postgres", "runtime-tokio-native-tls"] }
|
||||
|
||||
# Example: HTTP (uncomment if needed)
|
||||
# axum = "0.7"
|
||||
# reqwest = { version = "0.11", features = ["json"] }
|
||||
```
|
||||
|
||||
7. **Provide Usage Instructions**
|
||||
|
||||
After creating all files, tell the user:
|
||||
```
|
||||
✅ Hexagonal architecture structure initialized!
|
||||
|
||||
## Next Steps:
|
||||
|
||||
1. Review the generated structure in `src/`
|
||||
2. Define your domain models in `src/domain/models.rs`
|
||||
3. Implement business logic in `src/domain/services.rs`
|
||||
4. Create port traits in `src/ports/` for external dependencies
|
||||
5. Implement adapters in `src/adapters/` for each port
|
||||
|
||||
## Example Commands:
|
||||
- Add a new port: `/rust-hex-add-port`
|
||||
- Add an adapter: `/rust-hex-add-adapter`
|
||||
- Get architecture help: Ask the `rust-hex-architect` agent
|
||||
|
||||
## Running the Code:
|
||||
```bash
|
||||
cargo build
|
||||
cargo test
|
||||
```
|
||||
|
||||
## Architecture Layers:
|
||||
- **Domain**: Pure business logic (no external dependencies)
|
||||
- **Ports**: Trait definitions (interfaces)
|
||||
- **Adapters**: Concrete implementations
|
||||
|
||||
Dependencies flow: Adapters → Ports → Domain
|
||||
```
|
||||
|
||||
## Important Notes
|
||||
|
||||
- Create directories only if they don't exist
|
||||
- Don't overwrite existing files without asking the user first
|
||||
- If files already exist, ask if they want to merge or skip
|
||||
- Use proper Rust formatting and conventions
|
||||
- Add helpful comments explaining the architecture
|
||||
|
||||
## After Completion
|
||||
|
||||
Tell the user about the structure created and suggest next steps for their specific use case.
|
||||
Reference in New Issue
Block a user