Create Aspects

Aspects are the glue of composable architectures, combining components into cohesive platform features. They define how these pieces interact, leveraging UI, backend, and other runtimes to stitch together the necessary functionality.

Think of them as blueprints for your features:

  • Compose: Combine multiple components into a single, powerful feature.
  • Plug & Play: Easily add, remove, or replace Aspects within your platform.
  • Collaborate: Aspects can leverage each other's APIs for a seamless experience.

By connecting the right building blocks, Aspects bring your composable vision to life.

Feature aspects inverts integration to a platforms
Feature aspects inverts integration to a platforms

Create an aspect

Run the following command to create an Aspect:

bit create aspect people --scope myorg.people
CopiedCopy

This creates a "people" Aspect within the "myorg.people" scope. Aspects can manage both frontend and backend aspects of a feature. You'll be asked to choose the relevant runtimes (like Browser or Node.js) upon creation.

Compose to a platform

First step to start building your aspect is to connect it into an Harmony platform. If you haven't created one, use bit create harmony-platform or head to the Harmony platform docs.

After creating the platform, import the aspect in your platform composition and use it in the aspects property key:

// my-platform.bit-app.ts
import { SymphonyPlatformAspect } from '@bitdev/symphony.symphony-platform';
import { NodeJSRuntime } from '@bitdev/harmony.runtimes.nodejs-runtime';
import { BrowserRuntime } from '@bitdev/harmony.runtimes.browser-runtime';
import { HeaderAspect } from '@bitdev/symphony.aspects.header';
import { PeopleAspect } from '@myorg/people.people';

export const MyPlatform = HarmonyPlatform.from({
  name: 'my-platform',

  platform: [SymphonyPlatformAspect, {
    name: 'My new platform',
    logo: 'https://static.bit.dev/extensions-icons/pied-piper.svg',
  }],

  /**
   * runtimes supported by the platform.
  **/
  runtimes: [
    new NodeJSRuntime(),
    new BrowserRuntime()
  ],

  /**
   * list of aspects composed to the platform
  */
  aspects: [
    HeaderAspect,
    // add your new aspect here
    PeopleAspect
  ]
});

export default MyPlatform;
CopiedCopy

Aspects provide a specific API for each of the used runtimes by exposing it from the Aspect instance file, corresponding to people.*.runtime.ts. The below will guide through composing a NodeJS powered backend with a React frontend.

Build the backend

To implement your aspect's backend, edit the people.node.runtime.ts file. This is where you'll define the programmatic API. This API can interact with databases, external services, or even other Aspect APIs:

// people.node.runtime.ts
import { SymphonyPlatformAspect type SymphonyPlatformNode } from '@bitdev/symphony.symphony-platform';
import { User, createUserMock } from '@pied/people.entities.user';
import { PeopleServer } from './people-server.js';
import { peopleGqlSchema } from './people.graphql.js';

export class PeopleNode {
  constructor(
    /**
     * config of the people aspects. 
     * supplied by the platform composition and the default config.
    **/
    private config: PeopleConfig,
  ) {} 

  /**
   * expose an API for other aspects to use
   * on the class visible members.
  **/
  async listUsers(): Promise<User[]> {
    const peopleMock = createUserMock();

    return peopleMock.map((plainUser) => {
      return User.from(plainUser);
    });
  }

  /**
   * declare the aspect dependencies needed for your aspect.
   * the example below uses our default platform aspect, symphony as your dependency
  **/
  static dependencies = [SymphonyPlatformAspect];

  /**
   * the aspect provider is invoked upon app 
   * bootstrap returns the aspect instance.
   * 
   * aspects dependencies, config and slots 
   * are injected by harmony.
  **/
  static async provider([symphonyPlatform]: [SymphonyPlatformNode]) {
    const people = new PeopleNode(config, providerSlot);
    const peopleGqlSchema = peopleGqlSchema(people);

    // plugin a RESTful APIs and GraphQL schemas to a platform slot
    symphonyPlatform.registerBackendServer([
      {
        // define your express routes
        routes: [],
        // define your gql schema.
        gql: peopleGqlSchema
      }
    ]);

    return people;
  }
}
CopiedCopy

The example above is registering a new backend server to the Pied Piper platform with a GraphQL schema from implemented in the people.graphql.ts file:

// people.graphql.ts
import { gql } from 'graphql-tag';
import type { PeopleNode } from './people.node.runtime';

export function createPeopleGqlSchema(people: PeopleNode): any {
  return {
    typeDefs: gql`
      type Query {
        listUsers: [User]
      }
    `,
    resolvers: {
      Query: {
        listUsers: async () => {
          const user = await people.listUsers();
          return user.toObject();
        }
      }
    }
  };
}
CopiedCopy

The GraphQL service above will be forwarded with requests by the Symphony default backend gateway, which handles RESTful and GraphQL requests by default. You can create and register your own server and gateway implementation as needed.

Build the frontend

Below is an example of a People browser runtime, implemented in the people.browser.runtime.ts file. It is using our default platform aspect Symphony to register a new route to the pied piper platform, rendering the UserProfile React component:

// people.browser.runtime.ts
import { SymphonyPlatformAspect, type SymphonyPlatformBrowser } from '@bitdev/symphony.symphony-platform';
import { UserLobby } from '@pied/people.lobby.people-lobby';

export class PeopleBrowser {
  constructor(
    private badgeSlot: BadgeSlot
  ) {}

  // declere dependencies to use.
  static dependencies = [SymphonyPlatformAspect];

  // plugin your aspect to other aspect and initiate the aspect API.
  static async provider([symphonyPlatform]: [SymphonyPlatformBrowser], config, [providerSlot]: [ProviderSlot]) {
    const people = new PeopleBrowser();

    // plugin to slots in other aspects.
    symphonyPlatform.registerRoute([
      {
        path: '/users',
        component: () => {
          return <UserLobby />;
        }
      }
    ]);

    return people;
  }
}
CopiedCopy

The above code implementation is registering the User Lobby component which is requesting the backend implemented above using the Use User List React hook.

Aspect manifest

Aspect manifests are used to annotate static information for the Aspect build. It includes the mandatory id property including the Bit component ID:

import { Aspect } from '@bitdev/harmony.harmony';

export const PeopleAspect = Aspect.create({
  id: 'myorg.people/people'
});

export default PeopleAspect;
CopiedCopy

Testing Aspects

Aspects can be tested on each of the used runtimes. To test file to your runtime, add a people.[runtime-name].spec.ts. The example below demonstrates obtaining the testing the People aspect Browser runtime:

import { loadAspect } from '@bitdev/harmony.testing.load-aspect';
import type { PeopleBrowser } from './people.browser.runtime.js';
import { PeopleAspect } from './people.aspect.js';

it('should retrieve the aspect', async () => {
  const people = await loadAspect<PeopleBrowser>(PeopleAspect, {
    runtime: 'browser',
  });

  // use the runtime API.
  const userBadges = people.listUserBadges();

  expect(userBadges.length).toEqual(0);
});
CopiedCopy

Exposing API and types

Make sure to export all types in your Aspect index.ts file. It is not recommended to expose runtime files without using the type annotation from index files to avoid bundling unrelated code in your used runtimes.

import { PeopleAspect } from './people.aspect.js';

// export only types from specific runtimes.
export type { PeopleBrowser } from './people.browser.runtime.js';
export type { PeopleNode } from './people.node.runtime.js';

export default PeopleAspect;
export { PeopleAspect };
CopiedCopy

To load aspect for testing use the Load Aspect component to load your aspects into the test runtime.

Documentation

Aspects are documented using the Bit API reference and using MDX files for usage instructions and further documentation by default in the Aspect generator template.

Learn more