Skip to content

Aspect Oriented Programming (AOP) in JavaScript #99

Open

Description

@tonix-tuft

Ciao a tutti,

propongo un talk sull'AOP (Aspect Oriented Programming) in JavaScript, ho sviluppato una libreria apposita (AOPlà) che permette di facilitare il lavoro con i cosiddetti "cross-cutting concerns" e mi piacerebbe condividere la mia esperienza, sempre che l'argomento possa interessare a qualcuno.

Gli argomenti trattati:

  • Che cos'è l'AOP e quando può aver senso utilizzare questo paradigma lavorando con codice OOP;
  • Come creare aspetti utilizzando AOPlà e implementare funzionalità riusabili come caching, logging, ecc...;
  • Quali alternative ad AOPlà sono presenti ad oggi sempre nel mondo JavaScript e quali sono le principali differenze;
  • Under the hood, com'è implementata AOPlà e che cosa c'è effettivamente sotto il cofano: Proxy composition tramite ES6 Proxy API con pigretto, i decorator in JavaScript (legacy/stage-1 e stage-2), design pattern come Chain of Responsibility;

Di seguito un piccolo esempio di come si può implementare una funzionalità di caching (molto semplificata) senza e con la libreria.

Senza AOPlà:

class SomeService {
  /**
   * Sample cache.
   */
  cache = void 0;

  cacheInterval = void 0;

  /**
   * Sample method which performs a heavy computation, each time caching the result for subsequent calls
   * with a TTL (Time To Live) of 5 seconds.
   */
  async performHeavyComputation() {
    if (this.cache) {
      // Cache hit:
      console.log("Cache hit!");
      await this.cache;
      return;
    }
    // Cache miss:
    console.log("Cache miss...");

    // Business logic code:
    await new Promise(
      (resolve) => setTimeout(resolve, 8000) // Heavy computation timeout.
    );

    // Ugly cache management code mixed with business logic code:
    console.log("Caching.");
    this.cache = Promise.resolve();
    this.cacheInterval && clearInterval(this.cacheInterval);
    this.cacheInterval = setTimeout(() => {
      // Invalidating the cache after 5 seconds.
      console.log("Cache invalidation.");
      this.cache = void 0;
    }, 5000);
  }
}

// Run:
(async () => {
  const someService = new SomeService();

  // Cache miss.
  await someService
    .performHeavyComputation()
    .then(() => console.log(`Heavy computation completed.`));
  // Cache hit.
  await someService
    .performHeavyComputation()
    .then(() => console.log(`Heavy computation completed.`));

  // Waiting TTL expiration.
  await new Promise((resolve) => setTimeout(resolve, 6000));

  // Cache miss.
  await someService
    .performHeavyComputation()
    .then(() => console.log(`Heavy computation completed.`));
  // Cache hit.
  await someService
    .performHeavyComputation()
    .then(() => console.log(`Heavy computation completed.`));
})();

Con AOPlà:

import AOPla, { tag, aroundCall } from "aopla";

const Cacheable = tag("Cacheable");

class CacheAspect {
  cache = void 0;

  cacheInterval = void 0;

  @aroundCall(Cacheable)
  async advice({ proceed }) {
    if (this.cache) {
      // Cache hit:
      console.warn("Cache hit!");
      await this.cache;
      return;
    }
    // Cache miss:
    console.warn("Cache miss...");
    await proceed();
    // Cache management code only.
    console.warn("Caching.");
    this.cache = Promise.resolve();
    this.cacheInterval && clearInterval(this.cacheInterval);
    this.cacheInterval = setTimeout(() => {
      // Invalidating the cache after 5 seconds.
      console.warn("Cache invalidation.");
      this.cache = void 0;
    }, 5000);
  }
}

AOPla.registerAspects(CacheAspect);

class SomeService {
  /**
   * Sample method which performs a heavy computation, no other fancy code.
   */
  @Cacheable // Tagging this method as cacheable.
  async performHeavyComputation() {
    await new Promise(
      (resolve) => setTimeout(resolve, 8000) // Heavy computation timeout.
    );
  }
}

// Run:
(async () => {
  const someService = new SomeService();

  // Cache miss.
  await someService
    .performHeavyComputation()
    .then(() => console.log(`Heavy computation completed.`));
  // Cache hit.
  await someService
    .performHeavyComputation()
    .then(() => console.log(`Heavy computation completed.`));

  // Waiting TTL expiration.
  await new Promise((resolve) => setTimeout(resolve, 6000));

  // Cache miss.
  await someService
    .performHeavyComputation()
    .then(() => console.log(`Heavy computation completed.`));
  // Cache hit.
  await someService
    .performHeavyComputation()
    .then(() => console.log(`Heavy computation completed.`));
})();

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions