Components injected by abstract base, with timing and logging aspects

The complete source of dependencyInjection_components_and_aspects.ek9, compiled by the EK9 compiler. Shown on dependencyInjection.html, structure.html.

#!ek9
defines module site.dependencyInjection.componentsAndAspects

  defines component

    BaseConfiguration abstract
      getFileStoreName() abstract
        <- rtn as String?

      default operator ?

    Solution1 is BaseConfiguration
      override getFileStoreName() as
        <- rtn String: "MainStore"

    Solution2 extends BaseConfiguration
      override getFileStoreName() as
        <- rtn as String: "SecondaryStore"

    Solution3 extends BaseConfiguration
      storeName as String: "DefaultStore"

      override getFileStoreName() as
        <- rtn as String: storeName

      default operator ?

  defines class

    ConfigHandler
      //This component will get injected
      config as BaseConfiguration!

      showConfigDetails()
        stdout <- Stdout()
        stdout.println(config.getFileStoreName())

      default operator ?

  defines function

    checkConfigHandler()
      stdout <- Stdout()
      stdout.println("Will check config handler to see if file store name is available")
      configHandler <- ConfigHandler()
      configHandler.showConfigDetails()

  defines application

    noAspectApp
      register Solution1() as BaseConfiguration

    firstApp
      register FileLogger() as ILogger
      register Solution1() as BaseConfiguration with aspect of TimerAspect(SystemClock()), LoggingAspect("DEBUG")

    secondApp
      register FileLogger() as ILogger
      register Solution2() as BaseConfiguration with aspect of LoggingAspect("WARN")

    thirdApp
      register FileLogger() as ILogger
      register Solution3() as BaseConfiguration with aspect of TimerAspect(SystemClock())

  defines program

    Program0 with application of noAspectApp
      stdout <- Stdout()
      stdout.println("Program0 functionality")
      checkConfigHandler()

    Program1 with application of firstApp
      stdout <- Stdout()
      stdout.println("Program1 functionality")
      checkConfigHandler()

    Program2 with application of secondApp
      stdout <- Stdout()
      stdout.println("Program2 functionality")
      checkConfigHandler()

    Program3 with application of thirdApp
      stdout <- Stdout()
      stdout.println("Program3 functionality")
      checkConfigHandler()

  defines component

    ILogger as abstract
      log() as abstract
        ->
          level as String
          content as String

      default operator ?

    FileLogger extends ILogger
      stdout as Stdout: Stdout()
      override log() as
        ->
          level as String
          content as String
        //Just use Stdout for logging for this example.
        stdout.println(`${level}: ${content}`)

      default operator ?

  defines class

    LoggingAspect extends Aspect
      loggingLevel as String?

      LoggingAspect() as pure
        this("WARN")

      LoggingAspect() as pure
        -> level as String
        this.loggingLevel :=? level

      override beforeAdvice()
        -> joinPoint as JoinPoint
        <- rtn as PreparedMetaData: PreparedMetaData(joinPoint)
        //Will be injected
        logger as ILogger!
        logger.log(loggingLevel, `Before ${joinPoint.componentName()} ${joinPoint.methodName()}`)

      override afterAdvice()
        -> preparedMetaData as PreparedMetaData
        joinPoint <- preparedMetaData.joinPoint()
        //Will be injected
        logger as ILogger!
        logger.log(loggingLevel, `After ${joinPoint.componentName()} ${joinPoint.methodName()}`)

      default operator ?
    TimerData extends PreparedMetaData
      beforeOperation as Millisecond?

      default private TimerData()

      TimerData()
        ->
          millis as Millisecond
          joinPoint as JoinPoint
        super(joinPoint)
        beforeOperation: millis

      before()
        <- rtn as Millisecond: beforeOperation

      default operator ?
      default operator $
      default operator :=:
      default operator <=>

    TimerAspect extends Aspect
      clock as Clock?

      default private TimerAspect()

      TimerAspect()
        -> clock as Clock
        this.clock: clock

      override beforeAdvice()
        -> joinPoint as JoinPoint
        <- rtn as TimerData: TimerData(clock.millisecond(), joinPoint)

      //A handler for Aspect's 'afterAdvice() as dispatcher': it takes a TimerData, so it is a new overload, not an
      //override (front-end rationalisation N06), and EK9 dispatches a TimerData to it.
      afterAdvice()
        -> timerData as TimerData
        millisecondsTaken <- clock.millisecond() - timerData.before()
        joinPoint <- timerData.joinPoint()
        //Will be injected
        logger as ILogger!
        logger.log("INFO", `${millisecondsTaken} Milliseconds for ${joinPoint.componentName()} ${joinPoint.methodName()}`)

      default operator ?

Download dependencyInjection_components_and_aspects.ek9

Output

Program0

Program0 functionality
Will check config handler to see if file store name is available
MainStore

Program1

Program1 functionality
Will check config handler to see if file store name is available
DEBUG: Before site.dependencyInjection.componentsAndAspects::Solution1 getFileStoreName
DEBUG: After site.dependencyInjection.componentsAndAspects::Solution1 getFileStoreName
INFO: {{Millisecond}} Milliseconds for site.dependencyInjection.componentsAndAspects::Solution1 getFileStoreName
MainStore

Program2

Program2 functionality
Will check config handler to see if file store name is available
WARN: Before site.dependencyInjection.componentsAndAspects::Solution2 getFileStoreName
WARN: After site.dependencyInjection.componentsAndAspects::Solution2 getFileStoreName
SecondaryStore

Program3

Program3 functionality
Will check config handler to see if file store name is available
INFO: {{Millisecond}} Milliseconds for site.dependencyInjection.componentsAndAspects::Solution3 getFileStoreName
DefaultStore