The current state of the SPA SDK

We’re using the Bloomreach SPA SDK with the Bloomreach React SDK for the company I’m currently working at. I’ve worked with numerous SDKs and tools in the past, but this has to be the worst SDK I’ve ever worked with. The lack of developer documentation, the way the SDK is overly complex, and the amount of any types that are being used within the codebase is very problematic. The Bloomreach React SDK is still using class-based components even though functional components are the default standard. Even the React team has acknowledged that you should use functional components with hooks instead.

The way that the SDK expects the developer to pass the whole page object down the component tree is a terrible design decision and a flaw in the way the SDK has been architected. Why do I need to ask the page to get me the correct link and do I have to pass the page object five components down the tree? Why doesn’t a Bloomreach component simply accept the data that’s being returned from the Bloomreach API and store the rest of the page inside a useBloomreachPage hook that’s using a context behind the scenes? If a component down the tree needs Bloomreach-specific information, it can simply use the useBloomreachPage hook to get the data.

The SDK doesn’t allow developers to create type-safe applications as it isn’t even possible to use generics to determine the type of a component. Everything is an any-type in our codebase because the SDK simply doesn’t allow using generics to type the components, documents, menus, and other elements. Because of this, we have to use empty checks (?.) all over the place to make sure the data that’s being put inside the components are valid. This is a terrible developer experience and makes using TypeScript in our codebase almost impossible. The SDK should provide a set of simple types that the developers can expand upon.

The lack of documentation also doesn’t help with the state of the SDK. Take a look at the developer docs of Builder.io for instance. This is a golden standard that should be aimed for and makes it clearer how the SDK can be used in React or other frontend frameworks. Instead, you squeeze all the docs inside a single README.md file and call it a day. It shows the lack of care for other developers who want to integrate the SDK into their projects. This is also evident by the fact that you don’t allow the creation of issues in the GitHub project. I get it that the repo is mirrored, but disallowing this and only being able to improve the SDK by adding a lengthy post to these forums creates a more significant division between the developers who want to integrate and use the Bloomreach SDK in their project and its maintainers.

I tried understanding the code, but the level of unnecessary abstractions makes the Bloomreach SDK too complex to understand. Why doesn’t the SDK simply provide the correct types/interfaces and helper functions/hooks that can be used to map the Bloomreach API to component props for instance? Why is the whole page class a God object and has way too many responsibilities? I tried figuring out how the connection between the experience manager and the Bloomreach SDK is established, but after browsing through the code for more than an hour, I still have no clue how this is being done. The SDK is clearly designed by a backend developer who applies their knowledge to the front end and it shows. In my opinion, a frontend package should be procedural and not Object-Oriented. It should consist of a simple set of primitives, functions, and types. Just as with React, Vue, Solid, and/or Svelte.

In my opinion, the SDK should aim for simple-to-use helper functions for various parts of the application. getParametersFromComponent, mapModelToElementWithPage, or fetchBloomreachPage. Small and easy-to-understand functions; each with one goal and one responsibility. I’ve been able to rewrite a huge part of the application using these primitives, so it can be done and I can show how.

I just wanted to voice my concerns about the state of the Bloomreach SPA SDK and hopefully get something done to improve its state.

With kind regards,

Peter

1 Like

Hi @petervmeijgaard ,

I’m the current lead engineer of the SPA SDKs here at Bloomreach. First of all I want to thank you for the critical feedback, I’m always happy to take in suggestions and see if we can transform that into action. The goal in the end is of course to make life easier for developers integrating the SDKs into their apps.
Let me respond per section/remark.

The lack of developer documentation,

I think we immediately found common ground here. Its a known issue internally that we need to improve developer documentation beyond the long README’s that hold explanations per topic with code examples. Some ‘getting started’ tutorials etc would be great. I know Product management has this on the list of improvements.

still using class-based components even though functional components are the default standard

We would love to transform the SDK and examples to functional components. But its internal code detail of course, nothing is preventing devs from using functional components in their own SPA and using our components in there.

Why doesn’t a Bloomreach component simply accept the data that’s being returned from the Bloomreach API and store the rest of the page inside a useBloomreachPage hook that’s using a context behind the scenes

There is no need to pass down the page model through components using ‘prop drilling’. The Page object is stored in a BrPageContext that can be consumed (through the useContext hook if you wish).

Everything is an any -type in our codebase because the SDK simply doesn’t allow using generics to type the components, documents, menus, and other elements.

That statement really surprises me. The SDKs have extensive typescript interfaces and types exposed that you can build on. There are a lot of examples in the example SPA’s and the Reference SPA that show how you can create type safe code with the SDK. Are you aware of the published typedoc? index | @bloomreach/spa-sdk - v22.0.2
If this does not cover your use case could you give me an example of what is not possible using the types we provide?

Why doesn’t the SDK simply provide the correct types/interfaces and helper functions/hooks that can be used to map the Bloomreach API to component props for instance?

Does the BrProps interface not help you in this instance?

Why is the whole page class a God object and has way too many responsibilities?

The Page object is an object representation of the PageModel json that is returned from the Delivery API. Its really just meant as a helper to easily retrieve data/models from the page model. Could you expand a bit on what responsibilities you would split off?

The SDK is clearly designed by a backend developer who applies their knowledge to the front end and it shows. In my opinion, a frontend package should be procedural and not Object-Oriented.

Well I know the original author well and he’s a full stack engineer and one of the best that I know wrt code quality and clarity. He has set up the SDK with OOO principles using a dependency inversion/injection container that allows us to support the many different versions of brXM (both PaaS and SaaS) with the same library by switching out implementations during runtime based on the passed configuration while providing the correct types to consumers. I think its a rather brilliant setup that helps us attain our goals quite well. You are of course free to prefer procedural coding in your own projects.

the SDK should aim for simple-to-use helper functions for various parts of the application. getParametersFromComponent , mapModelToElementWithPage , or fetchBloomreachPage .

I agree with this and have discussed this with Product management as well, we should be able to make the retrieval of component data to render the templates easier. Its quite verbose at the moment. One note wrt fetchBloomreachPage, you can use the initialize function from the SPA SDK for that.

We might be able set up a meeting to discuss this in further detail? I’m happy to hear your suggestions for improvements and see the primitives you have used in your SPA.

kind regards,
Joeri de Gooijer

1 Like

Hi Joeri,

Thanks a lot for your quick reply and apologies for the harsh tone in my initial post. I read through it again and noticed I didn’t hold back any punches. A colleague of mine also pointed out that I sounded angry in my initial post. Again, my apologies.

I think we immediately found common ground here. Its a known issue internally that we need to improve developer documentation beyond the long README’s that hold explanations per topic with code examples. Some ‘getting started’ tutorials etc would be great. I know Product management has this on the list of improvements.

That’s great to hear! A decent developer guide and documentation on how to use the Bloomreach SDK would really go a long way. Good to hear that it’s being picked up.

We would love to transform the SDK and examples to functional components. But its internal code detail of course, nothing is preventing devs from using functional components in their own SPA and using our components in there.

Yes, I’m aware that teams have the flexibility to use functional components if they desire, but as someone who read through the code of the Bloomreach React SDK, it seems like as if the SDK isn’t using the latest coding standards that React recommends.

Are you aware of the published typedoc? index | @bloomreach/spa-sdk - v17.0.1

I wasn’t aware of the published type-doc, so thanks for sharing. I was talking about the internal types that are used within the SDK. Types like these https://github.com/bloomreach/spa-sdk/blob/main/packages/spa-sdk/src/page/component.ts#L54

Does the BrProps interface not help you in this instance?

Yes, it does work, but because the page-class adds more getters and setters than what’s returned from the resource API, the typed component, using BrProps with a generic, actually is more of a subset than the actual component that’s being passed in the React component. I could be wrong though, but I didn’t seem to get it working properly.

Could you expand a bit on what responsibilities you would split off?

Like I said before, I’d rather have a lot of small, reusable and testable functions than wrapping the response from the resource API in a class and adding a lot of getters and setters to it. getUrl, getButton, getVisitor, and getVisit, to name a few, feel like they shouldn’t be part of this class, but should be separate and smaller helper functions.

We might be able set up a meeting to discuss this in further detail? I’m happy to hear your suggestions for improvements and see the primitives you have used in your SPA.

I would love to! How can I reach out to schedule a meeting?

With kind regards,

Peter


Below a code example of how I’m rendering components from the Bloomreach resource API in a pet project:

import {
  getElementForComponent,
  ContainerItemElement,
  ElementModel,
} from '@bloomreach/core';
import { BrManagedContent, useBloomreachPage } from '@bloomreach/react';
import {
  HeaderDocument,
  HeaderDocumentType,
} from '@my-company/bloomreach-documents';
import React from 'react';

type Parameters = {
  useContentBean: boolean;
  document: string;
};

type Models = {
  // Element model being the reference to the document inside page
  // document: { $ref: string; }
  document: ElementModel;
};

// Because this component is a `ContainerItem`, the `'Header'` references `label`
// instead of the `name`
export type Component = ContainerItemElement<'Header', Parameters, Models>;

export const BrHeader = (props: Component) => {
  const page = useBloomreachPage();

  // Document is using the `HeaderDocumentType` type.
  const document = getElementForComponent<HeaderDocumentType>(page, props);

  return (
    <BrManagedContent>
      {document && <HeaderDocument {...document.data} />}
    </BrManagedContent>
  );
};

export default BrHeader;
// ContainerItemElement

export type ContainerItemElement<
  Label = string,
  Parameters = Record<string, unknown>,
  Models = Record<string, unknown> | undefined,
> = {
  type: 'container-item';
  id: string;
  name: string;
  label: Label;
  componentClass: string;
  models: Models;
  ctype?: string;
  links: {
    self: Link;
  };
  meta: {
    paramsInfo: Parameters;
    params: Record<string, unknown>;
  };
};

Hi Peter,

That you for sharing how you feel about not having issues available in GitHub, I never realized it could be seen as a way to avoid negative feedback. This was meant as a way to keep feedback contained to a single channel so that we avoid duplicates across the multiple feedback platforms and since GitHub is not used by all our products we went with this one.

I’ve enabled issues on the repository.
Thanks again for raising this and looking forward to your issues :slight_smile:

Cheers,
Daniel.

1 Like