DEV Community

Cover image for export class MicroFrontendRegistry
Bryan Ollendyke
Bryan Ollendyke

Posted on • Edited on

export class MicroFrontendRegistry

In the DDG example, there's a lot of ~magic~ in that call to MicroFrontendRegistry.call() which actually ensures the DDG endpoint is hit successfully. So let's de-magic a bit.

Deciding how to build the abstraction

Because we work so much with web components and the window.customElements global that makes the registry possible for frontend bricks, I decided to mirror it a bit when building a parallel concept for microservice endpoints.

The MicroFrontendRegistry consists mostly of:

  • providing a singleton interface so that only 1 registry exists no matter the timing of who declares it
  • making this queryable via the DOMor direct window access (personal preference, I always write my singletons this way)
  • set / add - new services so we can leverage them
  • has - verify a service is valid without calling it
  • call - execute request against the service
  • url - generate a URL to the service (useful for src building)

Definition of a service

Because I'm always trying to opt for maximal reuse across different contexts; I didn't assume that this was the only project I'd ever use these microservices in. I also don't want to assume these services will never conflict or that you'll always want to have @core/whatever as a service in your project.

As a result, we have services grouped into chunks of services that can be defined via single calls. Right now we have three groups that can be included easily as a shortcut to leveraging them.

import { enableServices } from '@lrnwebcomponents/micro-frontend-registry/lib/microServices.js';
// then enable the service groups in question
enableServices(['core', 'experimental', 'haxcms']);
Enter fullscreen mode Exit fullscreen mode
  • core - services that are common / expected / simple
  • experimental - ones for playing with / future consideration
  • haxcms - application specific things for haxcms

Again, these are just going to add a whole bunch of services quickly, but let's go back to the duck duck go example as to how the middleware class knew what to call and where it was.

DDG registration of microservice

// duckDuckGo
  MicroFrontendRegistry.add(
    {
      endpoint: '/api/services/website/duckDuckGo',
      name: '@core/duckDuckGo',
      title: 'Duck Duck Go',
      description: 'Search results from duck duck go',
      params: {
        q: "query param to search on"
      }
    }
  );
Enter fullscreen mode Exit fullscreen mode

Here we see we have an endpoint and the name. Title, description and parameters are mostly for developers to know / if we ever decide to visualize this information. In the case of DDG, all we do is pass in the query param q and we'll get this endpoint called and return our json blob of results. This simple association of endpoint to name will allow us to swap out and move the endpoint name at a later time if needed.

Uh... well when is it needed?

Funny you should ask! Here's what the next definition after DDG looks like:

  // screenshot - kept by itself bc of size of getBrowserInstance
  MicroFrontendRegistry.add({
    endpoint: "https://screenshoturl.elmsln.vercel.app/api/screenshotUrl",
    name: "@core/screenshotUrl",
    title: "Screenshot page",
    description: "Takes screenshot of a URL and returns image",
    params: {
      urlToCapture: "full url with https",
      quality: "Optional image quality parameter"
    }
  });
Enter fullscreen mode Exit fullscreen mode

😺 - Me building the screenshot service locally in vercel
😖 - Me in production trying to call it

The reason in this case, was that our screenshotUrl leverages puppeteer an awesome but heavy (file weight) library for running Chrome and other browsers headlessly. As a result of limitations with AWS lambda (which Vercel rides on top of) our monorepo couldn't have puppeteer running alongside everything else.

As a result, our paths needed changed as we pushed this functionality to it's own repository (and thus it could build independently and work without file size limits!). To make this change, I swapped out the original endpoint: "/api/media/web/screenshot", for the full URL to the production path. All the places we were calling@core/screenshotUrl` didn't change though!

By following a npm-esk methodology for naming, we can extend how we call these in the future, namespace nicely, and give a convention that people are used to, without directly referencing URLs which might change or need to be moved in the future.

The other reasons for moving are less interesting; like renaming, refactoring, etc.

Video time recapping these three articles

Here's a video stepping through the code and call structures to make DDG work end to end.

Top comments (0)