Realizing Cross-Cutting Concerns in Intent-Based Programming
By Ben Houston, 2025-05-02
Where Concerns Reside
One of the great promises of Aspect-Oriented Programming (AOP) was its ability to modularize cross-cutting concerns. Instead of scattering logging code across dozens of files, you could write a single aspect and "weave" it into the right places. It was elegant in theory, and in limited contexts (like Spring AOP), genuinely useful.
But over time, these concern modules became problematic. Their behavior was implicit, their injection points brittle, and their mental model too abstract for most developers.
Today, intent-based programming (IBP) revives a similar idea under a new name: generators. These aren't injected at runtime, but used during code generation to encapsulate reusable implementation logic. So the question becomes:
Should IBP generators carry forward the legacy of aspects? Or do we need a new kind of module to handle shared concerns?
This essay explores what worked -- and what failed -- in AOP's reusable modules, and how we can design better concern encapsulation for the LLM era.
Reusable Concerns in AOP: A Quick Primer
AspectJ allowed developers to define aspects -- modular units of behavior like:
- Logging
- Authentication
- Caching
- Metrics
- Transactions
These aspects were then injected at join points (method calls, field accesses, etc.) using pointcuts and advice.
This created clean code: no more repeated logger.debug()
lines. But it also introduced opaque behavior: you couldn't understand a method's true behavior by reading it.
How IBP Reframes the Problem
Intent-based programming shifts the execution model. We're not injecting behavior at runtime -- we're generating implementation code from declarative specs.
But we still face the same fundamental need: how do you handle common patterns and reusable concerns like auth, analytics, rate limiting, or retry logic -- without duplicating spec entries everywhere?
Enter the generator pattern.
In IBP, generators are pre-defined generators. They might output code for:
- A Stripe integration
- A D3 chart
- A login page using a specific UI library
- A REST endpoint with validation and authentication baked in
Example:
generator: stripe/standard-checkout options: currencies: [USD, EUR] methods: [credit, paypal]
But here's the catch: these generators are primary vertical. They handle feature-specific logic. What's missing is a way to modularize horizontal concerns -- like you could with aspects.
Should IBP Providers Evolve into Concern Modules?
Let's consider a real-world need: you're building multiple API endpoints, and they all require:
- Logging of request/response
- Rate limiting for anonymous users
- Metrics collection
- Input validation
Should you:
- Copy the same structured spec into each
.intent.yml
? - Use a generic generator for "standard API behavior"?
- Declare a reusable concern module that's composed with your intent spec?
This is where the limitations of the current generator model become apparent. Providers are usually one-per-component. They encode the overall generation strategy.
What we may need is a second kind of reusable module -- let's call it a concern module -- that can be composed orthogonally to generators.
Proposal: Introducing “Concern Modules” in IBP
What's a Concern Module?
A concern module is a reusable declarative unit that encapsulates behavior or constraints that apply across multiple components.
They don't generate full files. Instead, they inject augmentations into the code generated by a generator.
They're like the “advice” of AOP -- except declarative, inspectable, and applied at generation time.
Example: Logging Concern
concerns: - common/logging - common/auth-required
Each concern is:
- Composable: Can be applied to any component that supports the necessary hooks
- Auditable: Shows up in the generated diff
- Inspectable: Developers can read the concern spec like a normal YAML file
- Overrideable: Local component specs can override concern defaults
Example: common/logging.intent.yml
description: Add standard request/response logging appliesTo: [express/api-endpoint] injects: preHandler: | console.log("Request received", req.method, req.path); postHandler: | console.log("Response sent", res.statusCode);
This concern module could be applied across 10+ endpoints with a single line in their .intent.yml
.
Why Not Just Use More Providers?
You could technically create a generator: api/with-logging-and-auth
, but this quickly becomes combinatorially explosive:
with-logging
with-auth
with-rate-limit
with-logging-and-auth
with-logging-and-rate-limit
- etc.
This mirrors what happened in AOP when aspects became too fine-grained. The reuse became rigid, or required abstract join-point gymnastics.
Instead, concern modules give you orthogonal reuse: mix and match concerns as needed, on top of a stable generator base.
Design Goals for Concern Modules
If we pursue this model, we should learn from AOP's mistakes. Concern modules must be:
- Explicit: Clearly listed in the component's
.intent.yml
- Composable: Multiple concerns can be applied together without hidden conflicts
- Traceable: Their effects show up in generated diffs and comments
- Scoped: Concerns can target pre-defined hook points (e.g.,
preHandler
,postRender
,onError
) - Overrideable: Local intent specs can override or disable concern behavior
Provider + Concern: A Two-Tier Generation Model
Role | Purpose | Example |
---|---|---|
Provider | Full-featured generator for a component type | nextjs/page , sql/query , tanstack/form |
Concern Module | Inject reusable behavior into supported generators | common/logging , common/analytics , acme/a11y |
The mental model becomes simple:
- Use generators to scaffold features.
- Use concerns to apply shared behavior across features.
This separation mirrors how React has components and hooks. Each does a distinct job. Together, they allow both structure and behavior to be modularized cleanly.
Challenges and Open Questions
- When does a concern become a generator? If a concern modifies most of the generated output, it probably should be a generator.
- Do concerns depend on generator support? No. Because we rely upon LLM for code generation, generally most concerns can be mixed with most generators, although some combinations likely do not make sense to mix.
- Should concerns be code or declarative? We prefer declarative. But fallback to inline code templates for flexibility.
Conclusion: Learn from Aspects, Don't Repeat Them
AspectJ gave us a taste of modularized behavior -- but its opacity, rigidity, and tooling debt killed it in the long run.
Intent-based programming, with its declarative specs and LLM-powered generation, has a chance to redeem modular concerns -- if we do it right.
By introducing a second kind of module -- concerns -- alongside generators, we create a clear, composable, and developer-friendly way to handle behavioral reuse. These modules won't be woven into code invisibly. They'll be declared, diffed, validated, and reviewed -- like everything else in IBP.
The future of reusable software behavior isn't in advice and join points.
It's in inspectable intent, composable concern modules, and transparent code generation.