An aspect around a component

If you know AspectJ or Spring AOP you expect pointcuts, annotations and a proxy to configure; EK9 has none of them. An aspect is a class that extends Aspect, with advice that runs before and after a call, and it is attached where a component is registered in an Application - the component and its callers are untouched. audit.ek9 already has a Ledger and a SimpleLedger that prints what it posts. This lesson builds the aspect, the Application that applies it and a program, then runs them. To play it in the IDE: Help > Lessons > Applications and services…, or ek9 -ide -lesson aspects.

The EK9 IDE as the lesson opens

Start from

The code the lesson starts from: copy it into a file of your own and follow the steps.

#!ek9
defines module lessons.audit

  defines component

    Ledger as abstract
      post() as abstract
        -> amount as Integer

    SimpleLedger is Ledger
      override post()
        -> amount as Integer
        stdout <- Stdout()
        stdout.println(`posted ${amount}`)

Step 1: AuditAspect — the advice

Choose Code > New Construct > Class…

The EK9 IDE once this is done: Choose Code > New Construct > Class…

AuditAspect is a class like any other - what makes it an aspect is Extends Aspect. Type AuditAspect, put Aspect in Extends, and choose Preview.

The EK9 IDE just before choosing Preview: AuditAspect is a class like any other - what makes it an aspect is Extends Aspect

The preview shows AuditAspect extends Aspect about to be written. Choose Apply.

The EK9 IDE just before choosing Apply: The preview shows AuditAspect extends Aspect about to be written

Right-click the AuditAspect line and choose Add Method…

The EK9 IDE once this is done: Right-click the AuditAspect line and choose Add Method…

beforeAdvice runs before each call and is given a JoinPoint - which component and which method. Aspect already has it, so tick override; it answers with a PreparedMetaData, which afterAdvice is handed back. Add one parameter, joinPoint of type JoinPoint, set the return type to PreparedMetaData and choose Apply.

The EK9 IDE just before choosing Apply: beforeAdvice runs before each call and is given a JoinPoint - which component and which method

Select the stub, from <- rtn as PreparedMetaData? down to its //AI line, and type the advice: hand the JoinPoint back as the PreparedMetaData, and print which component and method are about to run. Saves when typed.

The EK9 IDE once this is done: Select the stub, from <- rtn as PreparedMetaData? down to its //AI line, and type the advice

Right-click the AuditAspect line and choose Add Method…

The EK9 IDE once this is done: Right-click the AuditAspect line and choose Add Method…

afterAdvice runs after the call and is handed the PreparedMetaData. Tick override, add one parameter, preparedMetaData of type PreparedMetaData, no return type, and choose Apply.

The EK9 IDE just before choosing Apply: afterAdvice runs after the call and is handed the PreparedMetaData

Select the stub, from require preparedMetaData? down to its //AI line, and type the advice: print that the call is over. Saves when typed.

The EK9 IDE once this is done: Select the stub, from require preparedMetaData? down to its //AI line, and type the advice

Step 2: Register it with the component

Choose Code > New Construct > Application…

The EK9 IDE once this is done: Choose Code > New Construct > Application…

AuditApp registers SimpleLedger as a Ledger, and the new With Aspect column adds 'with aspect of AuditAspect()' to that line - nothing about the aspect appears in Ledger or SimpleLedger. Type AuditApp, add one registration - Component SimpleLedger, As Interface Ledger, With Aspect AuditAspect - and choose Preview.

The EK9 IDE just before choosing Preview: AuditApp registers SimpleLedger as a Ledger, and the new With Aspect column adds 'with aspect of AuditAspect()' to that line - nothing about the aspect appears in Ledger or SimpleLedger

The preview shows the register line with its aspect: register SimpleLedger() as Ledger with aspect of AuditAspect(). A bare AuditAspect is written as a call, because an aspect can take constructor arguments. Choose Apply.

The EK9 IDE just before choosing Apply: The preview shows the register line with its aspect

Step 3: A program that calls the component

Choose Code > New Construct > Program…

The EK9 IDE once this is done: Choose Code > New Construct > Program…

PostPayment is the program's name. Bind it to AuditApp and inject ledger of type Ledger - the program asks for a Ledger and never mentions SimpleLedger or AuditAspect. Choose Preview.

The EK9 IDE just before choosing Preview: PostPayment is the program's name

The preview shows PostPayment about to be written, bound to AuditApp. Choose Apply.

The EK9 IDE just before choosing Apply: The preview shows PostPayment about to be written, bound to AuditApp

Select the stub, from ledgerInUse <- ledger down to its //AI line, and type the one call the program makes: post 25. Saves when typed.

The EK9 IDE once this is done: Select the stub, from ledgerInUse <- ledger down to its //AI line, and type the one call the program makes

Step 4: Run it

Choose Run > Run Program…

The EK9 IDE once this is done: Choose Run > Run Program…

The project's only program is already chosen. Choose Run.

The three lines are the advice around the component's own: before lessons.audit::SimpleLedger.post, then posted 25 from SimpleLedger itself, then after. The program only called ledger.post(25). Choose Close.

The EK9 IDE just before choosing Close: The three lines are the advice around the component's own

Step 5: Ask the IDE

Type /h aspect into the input bar above the editor and choose -> to send it - the same help ek9 -h aspect prints at a terminal.

The EK9 IDE once this is done: Type /h aspect into the input bar above the editor and choose -> to send it - the same help ek9 -h aspect prints at a terminal

The Help tab teaches the aspect: the two pieces of advice, the register line that applies it, and the ordering when several are listed - before advice in the order written, after advice in the reverse. /h Aspect, with a capital, is the type's own API.

The EK9 IDE once this is done: The Help tab teaches the aspect

The result

audit.ek9 now holds an AuditAspect whose advice runs either side of every call to a Ledger, an Application that registers SimpleLedger with that aspect, and a program that just asks for a Ledger - SimpleLedger never knew, and neither did the program. An aspect is a class, its registration is one 'with aspect of' on a register line, and the compiler checks both. ek9_new_construct, ek9_add_method, ek9_add_application (its registrations' aspects list) and ek9_add_program are what an AI assistant calls to do the same.

Where this lesson comes from