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 specific APIs for each of the platform's runtimes. They correspond to the two people.*.runtime.ts files in the new Aspect you created. This guide explains how to use both for composing a React frontend powered by a NodeJS backend.

Compose the frontend

Here is an example of a people browser runtime, implemented in the people.browser.runtime.ts file. It is using the default Symphony Platform Aspect to register a new route, 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
  ) {}

  // declare 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.

Compose 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 Aspects 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.
You extend or modify the gateway with your own tools as needed](/platform-engineering/customizing-platform#api-gateway).

Aspect manifest

Aspect manifests are used to annotate static information for the Aspect's 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

You can test each of the aspect's runtimes. To test a runtime, add a people.[runtime-name].spec.ts.
The example below demonstrates obtaining and 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.

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

Use the Load Aspect component to load your aspects into the test runtime, as shown above.

Documentation

Aspects are automatically documented using the Bit API reference. You can add additional documentation using MDX files. See automated API reference example, and additional MDX documentation.

Learn more