Create Aspects

Aspects are used for the compositon of a composable architecutre into a pluggable platform feature. You can compose Aspects in Harmony platforms, and use their APIs in other Aspects.

Run the following command to create an Aspect:

bit create aspect people --scope myorg.people
CopiedCopy

Aspects can tap to one or multiple platform runtimes to stitch the necessary building blocks required for your composable architecture.

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

Compose to a platform

Aspects are composed into Harmony platform, forming composable and unified platforms. It is useful to first use the Aspect before starting implementation to see the effect on the platform from the get go.

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: 'Acme',
    logo: 'https://static.bit.dev/extensions-icons/wayne.svg',
  }],

  runtimes: [
    new NodeJSRuntime(),
    new BrowserRuntime()
  ],

  aspects: [
    // use the default header to kickstart your platform.
    HeaderAspect,
    // your new aspect
    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 create the backend, add a people.node.runtime.ts file, and expose the programmatic API for the your aspect. You can use API methods to query your database of choice, make http requests to external APIs, or mutate and retrieve data and interact with other Aspect APIs:

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

export class PeopleNode {
  constructor(
    private config: PeopleConfig,
  ) {} 

  // expose a programmatic API for other aspects
  async listUsers(): Promise<User[]> {
    const peopleMock = createUserMock();

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

  // delcare our default platform aspect, symphony as your dependency
  static dependencies = [SymphonyPlatformAspect];

  static async provider([symphonyPlatform]: [SymphonyPlatformNode]) {
    const people = new PeopleNode(config, providerSlot);
    const peopleGqlSchema = peopleGqlSchema(people);

    symphonyPlatform.registerBackendServer([
      {
        // define your express routes
        routes: [],
        // define your gql schema.
        gql: peopleGqlSchema
      }
    ]);

    return people;
  }
}
CopiedCopy

This example is registering a new backend server to your 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

Our default platform, Symphony provides a default backend gateway proxing a service architecture, and default server for running GraphQL and Express APIs. You can build your own servers.

Build the frontend

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

// people.browser.runtime.ts
import { UserLobby } from '@wayne/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

Use your React component, using the GraphQL useQuery hook to list the users. See the User Lobby example here, and the Use People hook it uses to list users from the GraphQL API.

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