hexagonal-advisor
Reviews code architecture for hexagonal patterns, checks dependency directions, and suggests improvements for ports and adapters separation. Activates when users work with services, repositories, or architectural patterns.
What this skill does
# Hexagonal Architecture Advisor Skill
You are an expert at hexagonal architecture (ports and adapters) in Rust. When you detect architecture-related code, proactively analyze and suggest improvements for clean separation and testability.
## When to Activate
Activate this skill when you notice:
- Service or repository trait definitions
- Domain logic mixed with infrastructure concerns
- Direct database or HTTP client usage in business logic
- Questions about architecture, testing, or dependency injection
- Code that's hard to test due to tight coupling
## Architecture Checklist
### 1. Dependency Direction
**What to Look For**:
- Domain depending on infrastructure
- Business logic coupled to frameworks
- Inverted dependencies
**Bad Pattern**:
```rust
// ❌ Domain depends on infrastructure (Postgres)
pub struct UserService {
db: PgPool, // Direct dependency on PostgreSQL
}
impl UserService {
pub async fn create_user(&self, email: &str) -> Result<User, Error> {
// Domain logic mixed with SQL
sqlx::query("INSERT INTO users...")
.execute(&self.db)
.await?;
Ok(user)
}
}
```
**Good Pattern**:
```rust
// ✅ Domain depends only on port trait
#[async_trait]
pub trait UserRepository: Send + Sync {
async fn save(&self, user: &User) -> Result<(), DomainError>;
async fn find(&self, id: &UserId) -> Result<User, DomainError>;
}
pub struct UserService<R: UserRepository> {
repo: R, // Depends on abstraction
}
impl<R: UserRepository> UserService<R> {
pub fn new(repo: R) -> Self {
Self { repo }
}
pub async fn create_user(&self, email: &str) -> Result<User, DomainError> {
let user = User::new(email)?; // Domain validation
self.repo.save(&user).await?; // Infrastructure through port
Ok(user)
}
}
```
**Suggestion Template**:
```
Your domain logic directly depends on infrastructure. Create a port trait instead:
#[async_trait]
pub trait UserRepository: Send + Sync {
async fn save(&self, user: &User) -> Result<(), DomainError>;
}
pub struct UserService<R: UserRepository> {
repo: R,
}
This allows you to:
- Test with mock implementations
- Swap implementations without changing domain
- Keep domain pure and framework-agnostic
```
### 2. Port Definitions
**What to Look For**:
- Missing trait abstractions for external dependencies
- Concrete types in domain services
- Inconsistent port patterns
**Good Port Patterns**:
```rust
// Driven Port (Secondary) - What domain needs
#[async_trait]
pub trait UserRepository: Send + Sync {
async fn find(&self, id: &UserId) -> Result<User, DomainError>;
async fn save(&self, user: &User) -> Result<(), DomainError>;
async fn delete(&self, id: &UserId) -> Result<(), DomainError>;
}
// Driven Port for external services
#[async_trait]
pub trait EmailService: Send + Sync {
async fn send_welcome_email(&self, user: &User) -> Result<(), DomainError>;
}
// Driving Port (Primary) - What domain exposes
#[async_trait]
pub trait UserManagement: Send + Sync {
async fn register_user(&self, email: &str) -> Result<User, DomainError>;
async fn get_user(&self, id: &UserId) -> Result<User, DomainError>;
}
```
**Suggestion Template**:
```
Define clear port traits for your external dependencies:
// What your domain needs (driven port)
#[async_trait]
pub trait Repository: Send + Sync {
async fn operation(&self) -> Result<Data, Error>;
}
// What your domain exposes (driving port)
#[async_trait]
pub trait Service: Send + Sync {
async fn business_operation(&self) -> Result<Output, Error>;
}
```
### 3. Domain Purity
**What to Look For**:
- Framework types in domain models
- SQL, HTTP, or file I/O in domain logic
- Domain models with derive macros for serialization
**Bad Pattern**:
```rust
// ❌ Domain model coupled to frameworks
use sqlx::FromRow;
use serde::{Serialize, Deserialize};
#[derive(FromRow, Serialize, Deserialize)] // ❌ Infrastructure concerns
pub struct User {
pub id: i64, // ❌ Database type leaking
pub email: String,
pub created_at: chrono::DateTime<chrono::Utc>, // ❌ chrono in domain
}
```
**Good Pattern**:
```rust
// ✅ Pure domain model
pub struct User {
id: UserId, // Domain type
email: Email, // Domain value object
}
impl User {
pub fn new(email: String) -> Result<Self, ValidationError> {
let email = Email::try_from(email)?; // Domain validation
Ok(Self {
id: UserId::generate(),
email,
})
}
pub fn email(&self) -> &Email {
&self.email
}
}
// Adapter layer handles persistence
#[derive(sqlx::FromRow)]
struct UserRow {
id: i64,
email: String,
}
impl From<UserRow> for User {
fn from(row: UserRow) -> Self {
// Conversion in adapter layer
}
}
```
**Suggestion Template**:
```
Keep your domain models pure and framework-agnostic:
// Domain layer - no framework dependencies
pub struct User {
id: UserId,
email: Email,
}
// Adapter layer - handles framework concerns
#[derive(sqlx::FromRow)]
struct UserRow {
id: i64,
email: String,
}
impl From<UserRow> for User {
fn from(row: UserRow) -> Self {
// Convert database representation to domain
}
}
```
### 4. Adapter Implementation
**What to Look For**:
- Adapters not implementing ports
- Business logic in adapters
- Missing adapter layer
**Good Adapter Pattern**:
```rust
pub struct PostgresUserRepository {
pool: PgPool,
}
#[async_trait]
impl UserRepository for PostgresUserRepository {
async fn save(&self, user: &User) -> Result<(), DomainError> {
let row = UserRow::from(user); // Domain → Infrastructure
sqlx::query!(
"INSERT INTO users (id, email) VALUES ($1, $2)",
row.id,
row.email
)
.execute(&self.pool)
.await
.map_err(|e| DomainError::RepositoryError(e.to_string()))?;
Ok(())
}
async fn find(&self, id: &UserId) -> Result<User, DomainError> {
let row = sqlx::query_as!(
UserRow,
"SELECT id, email FROM users WHERE id = $1",
id.value()
)
.fetch_one(&self.pool)
.await
.map_err(|e| match e {
sqlx::Error::RowNotFound => DomainError::UserNotFound(id.to_string()),
_ => DomainError::RepositoryError(e.to_string()),
})?;
Ok(User::from(row)) // Infrastructure → Domain
}
}
```
**Suggestion Template**:
```
Implement your ports in the adapter layer:
pub struct PostgresRepo {
pool: PgPool,
}
#[async_trait]
impl MyPort for PostgresRepo {
async fn operation(&self, data: &DomainType) -> Result<(), Error> {
// Convert domain → infrastructure
let row = DbRow::from(data);
// Perform infrastructure operation
sqlx::query!("...").execute(&self.pool).await?;
Ok(())
}
}
```
### 5. Testing Strategy
**What to Look For**:
- Lack of test doubles
- Tests requiring real database
- Untestable domain logic
**Good Testing Pattern**:
```rust
#[cfg(test)]
mod tests {
use super::*;
use std::collections::HashMap;
// Mock repository for testing
struct MockUserRepository {
users: HashMap<UserId, User>,
}
impl MockUserRepository {
fn new() -> Self {
Self { users: HashMap::new() }
}
fn with_user(mut self, user: User) -> Self {
self.users.insert(user.id().clone(), user);
self
}
}
#[async_trait]
impl UserRepository for MockUserRepository {
async fn save(&self, user: &User) -> Result<(), DomainError> {
// Mock implementation
Ok(())
}
async fn find(&self, id: &UserId) -> Result<User, DomainError> {
self.users
.get(id)
.cloned()
.ok_or(DomainError::UserNotFound(id.to_string()))
}
}
#[tokio::tesRelated in General
modeling-omnistudio-epc-catalog
IncludedSalesforce Industries CME EPC product-modeling skill for Product2-based catalog creation. Use when creating EPC products, configuring product attributes, building offer bundles with Product Child Items, or reviewing EPC DataPack JSON metadata for product catalog changes. TRIGGER when: user creates or updates Product2 EPC records, AttributeAssignment payloads, AttributeMetadata/AttributeDefaultValues, Offer bundles, or ProductChildItem relationships. DO NOT TRIGGER when: designing OmniScripts/FlexCards/Integration Procedures (use building-omnistudio-omniscript, building-omnistudio-flexcard, or building-omnistudio-integration-procedure), implementing Apex business logic (use generating-apex), or troubleshooting deployment pipelines (use deploying-metadata).
relationship-science-coach
IncludedUse this skill for direct, practical adult relationship coaching: couples conflict, repair, trust, marriage, dating, flirting, attachment patterns, emotional connection, sex, desire differences, eroticism, kink negotiation, affection, love languages, breakups, and long-term passion. Draw on Gottman, EFT and Hold Me Tight, attachment science, modern sex research, Perel, Nagoski, Kerner, Schnarch, Love and Stosny, and flexible love-language tools. Be concrete and low-hedge. Redirect only for imminent danger, abuse, coercive control, minors, non-consent, self-harm, stalking, or medical/legal/psychiatric decisions.
building-sf-integrations
IncludedSalesforce integration architecture and runtime plumbing with 120-point scoring. Use this skill to set up Named Credentials, External Credentials, External Services, REST/SOAP callout patterns, Platform Events, and Change Data Capture. TRIGGER when: user sets up Named Credentials, External Services, REST/SOAP callouts, Platform Events, CDC, or touches .namedCredential-meta.xml files. DO NOT TRIGGER when: Connected App/OAuth config (use configuring-connected-apps), Apex-only logic (use generating-apex), or data import/export (use handling-sf-data).
venue-templates
IncludedAccess comprehensive LaTeX templates, formatting requirements, and submission guidelines for major scientific publication venues (Nature, Science, PLOS, IEEE, ACM), academic conferences (NeurIPS, ICML, CVPR, CHI), research posters, and grant proposals (NSF, NIH, DOE, DARPA). This skill should be used when preparing manuscripts for journal submission, conference papers, research posters, or grant proposals and need venue-specific formatting requirements and templates.
let-fate-decide
IncludedDraws the 12 Houses of the Zodiac Tarot spread to inject entropy into planning when prompts are vague, ambiguous, or casually delegated. Interprets the spread to guide next steps. Use when the user says 'let fate decide', 'YOLO', 'whatever', 'idk', or other nonchalant phrases, makes Yu-Gi-Oh references, or when you are about to arbitrarily pick between multiple reasonable approaches. Prefer over ask-questions-if-underspecified when the user's tone is casual or playful rather than precision-seeking.
net-ops
IncludedCross-platform network troubleshooting (Windows, macOS, Linux) via local or remote shell. Use for: DNS broken, can't resolve hostnames, nslookup/dig works but apps fail, NRPT, WFP, scutil, /etc/resolver, systemd-resolved, /etc/resolv.conf, NetworkManager, VPN DNS leak residue (ProtonVPN/Mullvad/WireGuard/AnyConnect), AV/firewall blocking DNS or DoH, Tailscale DNS interaction, intermittent connectivity, remote diagnostics over SSH.