In today’s fast-paced digital landscape, the need for adaptive, scalable, and maintainable software systems is greater than ever.
Traditional monolithic architectures often fall short in meeting these demands due to their rigidity and complexity. Enter Harmony, a framework designed to enable composable platforms with high efficiency and flexibility.
In this article, we'll explore how Harmony, powered by Bit, transforms the way we build and maintain modern applications.
What is Harmony?
Harmony is a minimalistic yet powerful dependency-injection framework tailored for creating composable architectures. By enabling developers to stitch together independently developed Bit components, Harmony allows teams to build applications that are not only modular but also adaptable to evolving business needs. Harmony supports full-stack composability, making it ideal for integrating frontend and backend functionalities into cohesive platforms.
Key Features of Harmony
Pluggable Aspects: Modular business features, called aspects, can be easily integrated into platforms.
Runtime Flexibility: Official support for Node.js and browser environments ensures compatibility across diverse use cases.
Why Composability Matters
Composability empowers organizations to:
Adapt Quickly: New features can be added or updated independently without disrupting the existing system.
Promote Reusability: Shared components can be leveraged across multiple projects, reducing duplication and increasing consistency.
Foster Collaboration: Teams can work on isolated aspects or components without stepping on each other’s toes.
Building a Composable System with Harmony
As mentioned, the building blocks of a Harmony system are Bit components. However, since Bit components are containers for any unit of software, not every Bit component will do.
Harmony uses components with a specific design to allow them to consume and provide "services" to and from other aspects. These can be frontend-only, backend-only, or full-stack features.
An aspect represents a single business capability that can be plugged into a larger system to form a full solution, a new application.
Aspects extend other aspects by registering to their ‘slot’ API. This inversion of control allows teams to compose features or business capabilities with minimal overhead since the aspect is responsible for the integration, not the system that composes it.
For example, the following Harmony app is an online shop for surfer clothing.
The team responsible for that online store decided to add a blog to their site. After searching for a suitable Aspects on the Bit platform, they came across this Blog aspect. Deciding they wanted to use it, they added it to their Hamrony application:
/**
* @coponentId: learnbit.apparel-waves/apparel-waves
* @filename: apparel-waves.bit-app.ts
*/
// imports...
import { SymphonyPlatformAspect } from '@bitdev/symphony.symphony-platform';
import { ApparelWavesPlatformAspect } from '@learnbit/apparel-waves.apparel-waves-platform';
import { BlogAspect } from '@learnbit/blog-pbc.blog';
export const ApparelWaves = HarmonyPlatform.from({
name: 'apparel-waves',
platform: [
/**
* ascpects register themsevles to the 'platform' aspect which
* is the entry point for this application
*/
SymphonyPlatformAspect,
{
name: 'Apparel Waves',
slogan: 'Making waves in fashion',
domain: 'apparel-waves.com',
},
],
/**
* aspects can run in multiple runtime environments. here, aspects
* provide functionalitis to the NodeJS services and to the web frontend apps
*/
runtimes: [new BrowserRuntime(), new NodeJSRuntime()],
aspects: [
/* 'apperal waves' aspect extends the system with its
* own unique functionalities. this aspect is maintained by
* a team that composes the apsects for their own solution.
*/
ApparelWavesPlatformAspect,
/**
* the blog aspect extends the system with content
* management capabilities. it is maintained by the blog PBC team.
*/
[
BlogAspect,
{
/**
* the blog aspect also provide a config api for this app to use
* in this case, since the the blog uses the Contenful platform,
* the fusion team need to provide it with their own Contentful space ID
*/
spaceId: 'contentful-spaceId',
},
],
],
});
export default ApparelWaves;
The Blog aspect registers itself to the platform in several ways:
It extends the system’s GraphQL schema with a node for content retrieval. This is done in the NodeJS Runtime.
It extends the system’s routing with the
/blog
route. This is done in the Browser Runtime.It extends the header with an additional item a ‘Blog’ link to
/blog
. This is done in the Browser Runtime.
NodeJS Runtime
/**
* @coponentId: learnbit.blog-pbc/blog
* @filename: blog.node.runtime.ts
*/
export class BlogNode {
constructor(private config: BlogConfig) {}
async getBlogPosts() {
const blogData = new BlogData(this.config);
return blogData.getBlogPosts();
}
static dependencies = [SymphonyPlatformAspect];
static async provider(
[symphonyPlatform]: [SymphonyPlatformNode],
config: BlogConfig
) {
const blog = new BlogNode(config);
const gqlSchema = blogGqlSchema(blog);
symphonyPlatform.registerBackendServer([
{
gql: gqlSchema,
},
]);
return blog;
}
}
export default BlogNode;
Browser Runtime
**
* @coponentId: learnbit.blog-pbc/blog
* @filename: blog.browser.runtime.ts
*/
export class BlogBrowser {
constructor(private config: BlogConfig) {}
static dependencies = [SymphonyPlatformAspect, HeaderAspect];
static async provider(
[symphonyPlatform, header]: [SymphonyPlatformBrowser, HeaderBrowser],
config: BlogConfig
) {
const blog = new BlogBrowser(config);
symphonyPlatform.registerRoute([
{
path: '/blog',
component: () => {
return (
<ApolloBlogProvider spaceId={config.spaceId}>
<BlogLobby />
</ApolloBlogProvider>
);
},
},
]);
header.registerLink([{ label: 'Blog', href: '/blog' }]);
return blog;
}
}
export default BlogBrowser;
The blog aspect in this example uses the Contentful content management system. It offers a coherent “language” for bought services within the company’s aspect ecosystem, ensuring they can communicate effectively and function seamlessly together.
/**
* @coponentId: learnbit.blog-pbc/blog
* @filename: blog-data.ts
*/
import { ApolloClient, InMemoryCache, HttpLink, gql } from '@apollo/client';
import type { BlogConfig } from './blog-config.js';
export class BlogData {
constructor(private readonly config: BlogConfig) {}
private contentfulClient = new ApolloClient({
link: new HttpLink({
uri: `https://graphql.contentful.com/content/v1/spaces/${this.config.spaceId}`,
headers: {
Authorization: `Bearer ${process.env.CONTENTFUL_ACCESS_TOKEN}`,
},
fetch,
}),
cache: new InMemoryCache(),
});
getBlogPosts = async () => {
const { data } = await this.contentfulClient.query({
query: gql`
query GetBlogs {
pageBlogPostCollection {
items {
title
slug
author {
name
}
}
}
}
`,
});
return data.pageBlogPostCollection.items.map((item) => ({
title: item.title,
slug: item.slug,
author: {
name: item.author ? item.author.name : null,
},
}));
};
}
Next steps
Explore the "Blog" scope (with the blog aspect)
Visit these Bit scopes to explore demo aspects and fork (copy) them to your Bit workspace to get started quickly.
Top comments (0)