Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

The Solution: Aspect-Oriented Programming

What is AOP?

Aspect-Oriented Programming (AOP) is a programming paradigm that provides a clean way to modularize crosscutting concerns. Instead of scattering logging/metrics/caching code throughout your application, you define aspects that are automatically woven into your code.

Key AOP Concepts

1. Aspect

A modular unit of crosscutting functionality (e.g., logging, timing, caching).

#![allow(unused)]
fn main() {
struct LoggingAspect;

impl Aspect for LoggingAspect {
    fn before(&self, ctx: &JoinPoint) {
        log::info!("→ {}", ctx.function_name);
    }

    fn after(&self, ctx: &JoinPoint, _result: &dyn Any) {
        log::info!("← {}", ctx.function_name);
    }
}
}

2. Join Point

A point in program execution where an aspect can be applied (e.g., function call).

#![allow(unused)]
fn main() {
pub struct JoinPoint {
    pub function_name: &'static str,
    pub module_path: &'static str,
    pub file: &'static str,
    pub line: u32,
}
}

3. Advice

Code that runs at a join point (before, after, around, after_throwing).

#![allow(unused)]
fn main() {
// Before advice - runs before function
fn before(&self, ctx: &JoinPoint) { ... }

// After advice - runs after function succeeds
fn after(&self, ctx: &JoinPoint, result: &dyn Any) { ... }

// Around advice - wraps function execution
fn around(&self, ctx: &mut ProceedingJoinPoint) -> Result<Box<dyn Any>, AspectError> {
    // Code before
    let result = ctx.proceed()?;
    // Code after
    Ok(result)
}

// After throwing - runs if function panics/errors
fn after_throwing(&self, ctx: &JoinPoint, error: &dyn Any) { ... }
}

4. Pointcut

A predicate that matches join points (where aspects apply).

#![allow(unused)]
fn main() {
// Phase 2: Per-function annotation
#[aspect(LoggingAspect::new())]
fn fetch_user(id: u64) -> User { ... }

// Phase 3: Pattern matching (future)
#[advice(
    pointcut = "execution(pub fn *(..)) && within(crate::api)",
    advice = "around"
)]
static LOGGER: LoggingAspect = LoggingAspect::new();
}

5. Weaving

The process of inserting aspect code into join points.

  • Compile-time weaving: Code generation during compilation (aspect-rs)
  • Load-time weaving: Bytecode modification at class load (AspectJ)
  • Runtime weaving: Dynamic proxies (Spring AOP)

How aspect-rs Solves the Problem

Before: Scattered Concerns

#![allow(unused)]
fn main() {
fn transfer_funds(from: Account, to: Account, amount: u64) -> Result<(), Error> {
    log::info!("Entering transfer_funds");
    let start = Instant::now();

    if !has_permission("transfer") {
        return Err(Error::Unauthorized);
    }

    let result = do_transfer(from, to, amount);

    log::info!("Exited in {:?}", start.elapsed());
    metrics::record("transfer_funds", start.elapsed());
    result
}

fn fetch_user(id: u64) -> Result<User, Error> {
    log::info!("Entering fetch_user");
    let start = Instant::now();

    if !has_permission("read") {
        return Err(Error::Unauthorized);
    }

    let result = database::query_user(id);

    log::info!("Exited in {:?}", start.elapsed());
    metrics::record("fetch_user", start.elapsed());
    result
}

// ... 50 more functions with duplicated code ...
}

After: Modular Aspects

#![allow(unused)]
fn main() {
// Define aspects once
#[aspect(LoggingAspect::new())]
#[aspect(TimingAspect::new())]
#[aspect(AuthorizationAspect::require_role("admin", get_roles))]
#[aspect(MetricsAspect::new())]
fn transfer_funds(from: Account, to: Account, amount: u64) -> Result<(), Error> {
    do_transfer(from, to, amount)  // Pure business logic!
}

#[aspect(LoggingAspect::new())]
#[aspect(TimingAspect::new())]
#[aspect(AuthorizationAspect::require_role("user", get_roles))]
#[aspect(MetricsAspect::new())]
fn fetch_user(id: u64) -> Result<User, Error> {
    database::query_user(id)  // Pure business logic!
}
}

Benefits:

  • Clean code: Business logic is immediately visible
  • Reusable: Aspects defined once, applied everywhere
  • Maintainable: Change logging format in one place
  • Safe: Can’t forget to apply aspects (compile-time weaving)
  • Fast: Zero runtime overhead (<10ns)

Generated Code

The #[aspect(...)] macro generates this code at compile time:

#![allow(unused)]
fn main() {
// Original
#[aspect(LoggingAspect::new())]
fn fetch_user(id: u64) -> User {
    database::query_user(id)
}

// Generated (simplified)
fn fetch_user(id: u64) -> User {
    let __aspect = LoggingAspect::new();
    let __ctx = JoinPoint {
        function_name: "fetch_user",
        module_path: module_path!(),
        file: file!(),
        line: line!(),
    };

    // Before advice
    __aspect.before(&__ctx);

    // Original function body
    let __result = database::query_user(id);

    // After advice
    __aspect.after(&__ctx, &__result);

    __result
}
}

Key point: This happens at compile time, so there’s no runtime overhead for the aspect framework itself.

Real-World Impact

Code Reduction

A microservice with 50 API endpoints:

  • Before: 1,500 lines of duplicated logging/metrics/auth code
  • After: 8 aspects (200 lines total) + 50 annotations (50 lines) = 250 lines
  • Reduction: 83% less code!

Maintenance

Need to change log format?

  • Before: Touch all 50 files, risk introducing bugs
  • After: Change 1 line in LoggingAspect, recompile

Safety

Authorization must be checked on all admin endpoints:

  • Before: Manually add checks, hope you don’t forget
  • After: Aspect applied declaratively, compiler ensures it’s woven

What Makes aspect-rs Special?

Unlike traditional AOP frameworks:

  1. Compile-time weaving: No runtime aspect framework overhead
  2. Type-safe: Full Rust type checking, ownership verification
  3. Zero-cost abstraction: Generated code is as fast as hand-written
  4. Three-phase approach: Start simple, upgrade to automatic weaving
  5. Production-ready: 8 standard aspects included

In the next sections, we’ll explore how aspect-rs compares to AspectJ (the Java AOP framework) and why it’s the right choice for Rust.