DEV Community

Cover image for Composable Backend-for-Frontend
Eden Ella for Bit

Posted on

Composable Backend-for-Frontend

The Backend-for-Frontend (BFF) pattern is an architectural approach where a dedicated backend service is created for each frontend application or client type.

Utilizing the BFF pattern allows developers to tailor the APIs and backend services to the specific requirements of each client. This can result in improved performance, better security controls, and a more cohesive user experience.

For the BFF architecture to be effective and sustainable, it has to be composable. BFFs should be assembled using loosely coupled, interoperable components that can be easily combined, reused, or replaced.


A "BFF component" in a composable system is a standardized abstract layer for specific technical tasks or services built in-house or bought from different vendors.

Components in this system should adhere to "rules of communication" agreed upon across teams to make them easily pluggable into the wider system architecture.

A well-designed system of "BFF components" facilitates innovation and allows businesses to quickly respond to new opportunities or threats.


In this article, we’ll explore Bit Harmony, an innovative framework for the composition of full-stack components using smart dependency injection. The platform uses Bit components as its building blocks.

Image description

Image description

Visit the "LazyWorks Inc." Bit organization to see a demo of this approach.

Image description

Aspects

An aspect is the building block of a Harmony solution. It is a full-stack Bit component that provides UI and backend “services” for other aspects.

An aspect represents a single feature that can be plugged into a larger system to form a full solution, a new application.

Image description

Aspects extend other aspects by registering to their ‘slot’ API. This inversion of control allows teams to compose features 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 lazy living. The team responsible for that online store decided to add a blog to their site. After searching for a suitable aspect on the Bit platform, they came across this Contentful-based Blog aspect. Deciding they wanted to use it, they added it to their Hamrony application:



/**
 * @coponentId: lazyworks.no-sweat-shop/no-sweat-shop
 * @filename: no-sweat-shop.bit-app.ts
 */ 

// imports...

/**
 * The "LazyWorks Platform" defines how an 
 * aspect can integrate into the system 
 * (i,e, register new routes, 
 * extend the GraphQL schema, etc) 
 */
import { LazyworksPlatformAspect } from '@lazyworks/platform.lazyworks-platform';
import { NoSweatShopPlatformAspect } from '@lazyworks/no-sweat-shop.no-sweat-shop-platform';
import { BlogAspect } from '@lazyworks/blog.blog';

export const NoSweatShop = HarmonyPlatform.from({
  name: 'no-sweat-shop',
  platform: [
   /**
    * aspects register themselves to the 'platform' aspect which
    * is the entry point for this application
    */
    LazyworksPlatformAspect,
    {
      name: "No Sweat Shop",
      slogan: "Effortless Living, Delivered.",
      domain: "no-sweat-shop.com",
      logo: "https://static.bit.dev/extensions-icons/my-project.svg",
    },
  ],
  /**
    * aspects can run in multiple runtime environments. here, aspects 
    * provide functionalities to the NodeJS services and the web frontend apps
   */
  runtimes: [new BrowserRuntime(), new NodeJSRuntime()],

  aspects: [
    /* 'no sweat shop' aspect extends the system with its 
     * own unique functionalities. this aspect is maintained by 
     * a team that composes the aspects for their own solution.
     */
    NoSweatShopPlatformAspect,
    /**
     * the blog aspect extends the system with content 
     * management capabilities.
     */
    [
      BlogAspect,
      {
       /**
         * the blog aspect also provides a config API for this app to use
         * in this case, since the blog uses the Contentful platform,
         * the team needs to provide it with their own Contentful space ID
         */        
        spaceId: 'contentful-spaceId',
      },
    ],
  ],
});

export default NoSweatShop;


Enter fullscreen mode Exit fullscreen mode

Image description

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.

Note that the UI provided by the aspects is optional. In some cases, depending on the type of application the BFF is serving, the browser-runtime features might not be used

NodeJS Runtime



/**
 * @coponentId: lazyworks.blog/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 = [LazyworksPlatformAspect];

  static async provider(
    [lazyworksPlatform]: [LazyworksPlatformAspect],
    config: BlogConfig
  ) {
    const blog = new BlogNode(config);
    const gqlSchema = blogGqlSchema(blog);

    lazyworksPlatform.registerBackendServer([
      {
        gql: gqlSchema,
      },
    ]);

    return blog;
  }
}

export default BlogNode


Enter fullscreen mode Exit fullscreen mode

Browser Runtime



/**
 * @coponentId: lazyworks.blog/blog
 * @filename: blog.browser.runtime.ts
 */


export class BlogBrowser {
  constructor(private config: BlogConfig) {}

  static dependencies = [LazyworksPlatformAspect, HeaderAspect];

  static async provider(
    [lazyworksPlatform, header]: [LazyworksPlatformBrowser, HeaderBrowser],
    config: BlogConfig
  ) {
    const blog = new BlogBrowser(config);

    lazyworksPlatform.registerRoute([
      {
        path: '/blog',
        component: () => {
          return (
            <ApolloBlogProvider spaceId={config.spaceId}>
              <BlogLobby />
            </ApolloBlogProvider>
          );
        },
      },
    ]);

    header.registerLink([{ label: 'Blog', href: '/blog' }]);

    return blog;
  }
}

export default BlogBrowser;


Enter fullscreen mode Exit fullscreen mode

To learn more, visit Bit's documentation site or the Bit Platform.

Top comments (0)