lisachenko/golang-aop-framework

Go! AOP Port to the GoLang framework

★ 0Forks 0GoGitHub ↗Compare

README

Go! AOP for Go

A port of the concept of goaop/framework — an aspect-oriented programming framework — from PHP to Go.

AOP extends the language with the ability to intercept method executions declaratively, without touching the intercepted code: cross-cutting concerns (logging, caching, transactions, metrics, security checks) live in aspects, and a weaver wires them into the application.

Concept

The PHP framework performs load-time source weaving: it intercepts class loading, rewrites the class into a __AopProxied trait, and generates a proxy class that runs an interceptor chain around the original method bodies. All pointcut matching and advice ordering happen statically at weave time; the generated code contains zero runtime checks.

Go has no runtime class loading, so this port moves the same pipeline entirely to build time:

  1. The goaop CLI loads your packages with go/packages (full syntax + type information).
  2. Aspects are discovered by //aop: comment directives — the Go analogue of PHP attributes.
  3. Pointcut expressions (the same DSL, adapted to Go syntax) are matched statically against packages, receiver types, and methods — two-stage, like the PHP Pointcut contract: context first to prune cheaply, then members.
  4. For every matched method the weaver writes a shadow copy of the source file where only the declaration identifier is renamed (Greet → __aop_Greet) — the analogue of the class → trait rewrite — and generates a zz_goaop_woven.go file per package containing a proxy method with the original name that runs the ordered interceptor chain, terminating in the renamed original.
  5. An overlay.json maps original file paths to the shadow tree, and the build runs as go build -overlay — original sources are never modified.
goaop run ./examples/demo      # weave + run
go run ./examples/demo         # plain run, no aspects — zero runtime intrusion

The engine (contracts, pointcut matcher, rewriter) is independent from the overlay driver, so a transparent -toolexec driver can be added later without redesign.

Writing an aspect

//aop:aspect
type LoggingAspect struct{}

//aop:pointcut("execution(exported **.Service.*(*))")
func (a *LoggingAspect) serviceMethods() {}

//aop:before("serviceMethods")
func (a *LoggingAspect) LogEntry(inv aop.MethodInvocation) {
    log.Printf("calling %s args=%v", inv, inv.Arguments())
}

//aop:around("execution(**.Service.Greet(*))", order=10)
func (a *LoggingAspect) Time(inv aop.MethodInvocation) error {
    start := time.Now()
    err := inv.Proceed()
    log.Printf("%s took %s", inv, time.Since(start))
    return err
}

//aop:afterthrowing("serviceMethods")
func (a *LoggingAspect) LogError(inv aop.MethodInvocation, err error) {
    log.Printf("%s failed: %v", inv, err)
}

Advice kinds and their exact semantics are ported from the PHP framework:

Kind Semantics
before advice runs, then the chain proceeds
after chain proceeds; advice runs in a defer (i.e. finally — also on panic)
around advice fully controls the invocation and calls Proceed() itself
afterthrowing fires on a non-nil trailing error result or a panic (wrapped as *aop.PanicError, re-panicked after the advice)

Advice ordering (before → after → around, ties broken by order=N) is decided at weave time and baked into the generated chain.

Pointcut expression language

The PHP DSL adapted to Go:

execution(exported **.Service.Greet*(*))
execution(github.com/acme/**/demo.*Repository.save|update(*))
within(github.com/acme/app/**) && !within(**/internal/**)
LoggingAspect.serviceMethods              // named pointcut reference
  • ** matches across / and .; * matches one identifier / path segment; ? one character; | is alternation inside a name.
  • The last two .-separated components are the receiver-type and method patterns; everything before is the package import-path pattern (omit it to match any package).
  • exported / unexported replace PHP's public / protected|private.
  • &&, ||, ! combine pointcuts; named pointcuts declared with //aop:pointcut can be referenced by Aspect.name or name within the same aspect.
  • The argument list is always (*) in this prototype, as in the PHP framework.

Status

Prototype. Method-execution interception only; property access, function, constructor/init interception and introductions from the PHP framework are out of scope for now (property interception is not portable to Go at all).

Packages

Package Role
aop runtime contracts: joinpoints, interceptor chain, registry
aop/pointcut weave-time pointcut model, combinators, expression parser
weaver driver-independent engine: discovery, matching, orchestration
weaver/rewrite source rename + proxy code generation
weaver/overlay shadow tree + go build -overlay driver
cmd/goaop the CLI: `goaop build

Contributors

lisachenko

Issues