Types of Programming

Aspect-Oriented Programming

Separating cross-cutting concerns from core business logic.

Overview

Aspect-Oriented Programming (AOP) addresses cross-cutting concerns: functionality like logging, security checks, caching, and transaction management that cuts across multiple modules. AOP separates these concerns into "aspects" that are woven into the program at defined join points. This keeps business logic clean and avoids duplicating infrastructure code throughout the codebase.

Origin

Gregor Kiczales and colleagues at Xerox PARC published the foundational AOP paper in 1997. AspectJ (1998) was the first widely-used AOP language extension for Java. Spring Framework adopted AOP for transaction management and security in its 2003 release, making it a mainstream enterprise pattern.

Examples

Method interception via decorators in TypeScript

function Log(target: object, key: string, descriptor: PropertyDescriptor) {
  const original = descriptor.value;
  descriptor.value = async function (...args: unknown[]) {
    const start = performance.now();
    console.log('[' + key + '] called with', JSON.stringify(args));
    try {
      const result = await original.apply(this, args);
      const ms = (performance.now() - start).toFixed(2);
      console.log('[' + key + '] completed in ' + ms + 'ms');
      return result;
    } catch (err) {
      console.error('[' + key + '] failed:', err);
      throw err;
    }
  };
  return descriptor;
}

class OrderService {
  @Log
  async placeOrder(userId: number, items: string[]): Promise<string> {
    // business logic only; no logging code here
    return 'order-' + Date.now();
  }
}

TypeScript decorators (stage 3 proposal, enabled with experimentalDecorators or using the new TC39 decorator spec) weave logging around the method without modifying the method body. The business logic is untouched.

Around advice pattern in Ruby using prepend

module TransactionAspect
  def save(record)
    DB.transaction do
      result = super
      AuditLog.record(action: 'save', record_id: record.id, user: Current.user)
      result
    end
  end
end

module CacheAspect
  def find(id)
    cache_key = "record:#{id}"
    cached = Redis.current.get(cache_key)
    return JSON.parse(cached) if cached

    result = super
    Redis.current.setex(cache_key, 300, result.to_json)
    result
  end
end

class RecordRepository
  prepend TransactionAspect
  prepend CacheAspect

  def save(record) = DB[:records].insert(record.attributes)
  def find(id) = DB[:records].where(id: id).first
end

Ruby prepend inserts the module before the class in the method lookup chain, so calling super reaches the original method. This is how ActiveRecord callbacks and Mongoid middleware are implemented.

Use Cases

  • 01Transaction demarcation in Spring or Rails where @Transactional / ActiveRecord callbacks wrap persistence logic without cluttering service methods
  • 02Security enforcement: Spring Security's @PreAuthorize intercepts method calls before execution to verify permissions
  • 03Performance instrumentation: timing and metrics collection applied uniformly across all service methods without modifying each one
  • 04Request/response logging middleware in Rack, Express, or ASP.NET Core where every HTTP call is intercepted at the framework level

When Not to Use

  • //When the cross-cutting behaviour needs to vary per-call based on arguments; aspects apply uniformly and become awkward when conditional weaving is needed
  • //Debugging complex systems where aspects make the actual execution path non-obvious from the source code alone
  • //Small projects where a direct function call to a logger or transaction helper is clearer than an implicit weaving mechanism

Technical Notes

  • AspectJ supports compile-time, post-compile, and load-time weaving. Spring AOP uses runtime proxies (JDK dynamic proxies for interfaces; CGLIB subclasses for classes), which cannot intercept self-calls within the same object
  • Ruby's Module#prepend (introduced in Ruby 2.0) is the idiomatic AOP mechanism. include appends to the ancestor chain; prepend inserts before. The distinction matters for super call routing
  • TypeScript legacy decorators (experimentalDecorators) and the new TC39 Stage 3 decorator spec differ in how they access the descriptor; migrating between them requires code changes
  • Pointcut expressions in AspectJ use a rich language: execution(* com.example.service.*.*(..)) matches all methods in the service package. Overly broad pointcuts are a common source of unintended interception