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
Aspects can tap to one or multiple platform runtimes to stitch the necessary building blocks required for your composable architecture.
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;
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.
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; } }
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(); } } } }; }
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.
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; } }
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 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;
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); });
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 };
To load aspect for testing use the Load Aspect component to load your aspects into the test runtime.
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.