Skip to main contentIBM Cloud-Native

Inventory BFF

Develop and deploy the BFF component of the inventory application

Setup

The Inventory BFF’s role in the architecture is to act as an orchestrator between the core business services and the specific digital channel it is focused on supporting. This class article will give you more detail about the architectural pattern and the benefits. Backend for Frontend

The Inventory solution will use GraphQL for its BFF layer, which enables the API to be dynamically controlled from the client using API queries. Follow the steps below to get started.

Setup

To get the initial BFF project created and registered with a pipeline for automated builds follow these steps.

  • Create a new repository from the Typescript GraphQL Code Pattern

    • In order to prevent naming collisions, name the repository inventory-management-bff-{your initials} replacing {your initials} with your actual initials.
  • Clone the new repository to your machine

  • Run npm install to install all the package dependencies

  • Go into the repository directory cloned and execute the following

    oc sync dev-{your initials} --dev
  • Register the pipeline register the pipeline

    oc pipeline --tekton

    replacing {your initials} with your actual initials

  • When the pipeline is completed, run oc endpoints -n dev-{your initials}. You should see an entry for the app we just pushed. Select the entry and hit Enter to launch the browser, if you are working on your desktop/laptop. Otherwise copy the url and paste it in a new browser tab.

Build the controller for the REST interface

The controller provides the REST interface for our BFF. The Code Pattern uses the typescript-rest package to simplify the tasks required to create a controller.

  • Start the tests in tdd mode by running
npm run tdd
  • Create the controller test
test/controllers/stock-items.controller.spec.ts
import {Application} from 'express';
import * as request from 'supertest';
import {buildApiServer} from '../helper';
describe('stock-item.controller', () => {
let app: Application;
beforeEach(async () => {
  • Create the controller component
src/controllers/stock-items.controller.ts
import {GET, Path} from 'typescript-rest';
@Path('stock-items')
export class StockItemsController {
@GET
async listStockItems(): Promise<any[]> {
return [];
}
  • Add the controller to the controllers index.ts. (Using index.ts is a good way to manage which components are exposed by a component and provide a good way to load the modules that will be injected into other components)
src/controllers/index.ts
export * from './health.controller';
export * from './stock-items.controller';
  • Start the service to see it running
npm start
- Open a browser to `http://localhost:3000/api-docs` to see the swagger page
  • Expand our service from the list, click Try it out, then click Execute

  • Push the changes we’ve made to the repository

git add .
git commit -m "Adds stock items controller"
git push

Update the controller to call a service

The pattern recommended for the REST controllers is to let it focus on translating REST protocols into javascript and to put the business logic in a separate service component.

  • Add a StockItem model that contains the values needed for the UI
src/models/stock-item.model.ts
export class StockItemModel {
id: string;
name: string;
description: string;
stock: number;
unitPrice: number;
picture: string;
manufacturer: string;
}
  • Register the model with the index.ts file in the models directory. Append this to end of the file.
src/models/index.ts
...
export * from './stock-item.model';
  • Define an abstract class to provide the interface for our API
src/services/stock-items.api.ts
import {StockItemModel} from '../models';
export abstract class StockItemsApi {
async abstract listStockItems(): Promise<StockItemModel[]>;
}
  • Add the abstract class to the index.ts file in the services directory. Add it to the end of other export statements, do not overwrite the file.
src/services/index.ts
...
export * from './stock-items.api';
...
  • Update the controller test to inject the service into the controller and to return the value from the service
test/controllers/stock-items.controller.spec.ts
import {Application} from 'express';
import * as request from 'supertest';
import {Container} from 'typescript-ioc';
import {buildApiServer} from '../helper';
import Mock = jest.Mock;
import {StockItemsMockService} from '../../src/services';
describe('stock-item.controller', () => {
  • Update the controller to inject the service and use it
src/controllers/stock-items.controller.ts
import {Inject} from 'typescript-ioc';
import {GET, Path} from 'typescript-rest';
import {HttpError} from 'typescript-rest/dist/server/model/errors';
import {StockItemModel} from '../models';
import {StockItemsMockService} from '../services';
class BadGateway extends HttpError {
constructor(message?: string) {

Create a mock service implementation

Now that we have our Controller using our API to get the data, lets create an implementation that will provide mock data for now.

  • Add a stock-items-mock.service to services
src/services/stock-items-mock.service.ts
import {StockItemsApi} from './stock-items.api';
import {StockItemModel} from '../models';
export class StockItemsMockService implements StockItemsApi {
async listStockItems(): Promise<StockItemModel[]> {
return [
{
id: "1",
  • Add the mock service to the index.ts file in the services directory
src/services/index.ts
...
export * from './stock-items-mock.service';
...
  • Start the service
npm start
- Open a browser to `http://localhost:3000/api-docs` and execute the stock items controller. You should see the data from above returned by the service.
  • Push the changes we’ve made to the repository
git add .
git commit -m "Adds a mock service implementation"
git push

Add a GraphQL implementation of Stock Items

The GraphQL Code Pattern supports both REST and GraphQL APIs for accessing backend services. We created a REST controller to expose the results from the service and now we will do the same for GraphQL.

  • Create a stock-items GraphQL schema in the schemas directory
src/schemas/stock-item.schema.ts
import {Field, Float, Int, ObjectType} from 'type-graphql';
import {StockItemModel} from '../models';
@ObjectType()
export class StockItem implements StockItemModel {
@Field()
id: string;
@Field()
description: string;
  • Add the stock-items schema to the index.ts in the schemas directory
src/schemas/index.ts
export * from './stock-item.schema'
  • Add a ‘stock-item’ GraphQL resolver in the resolvers directory
src/resolvers/stock-item.resolver.ts
import {Query, Resolver} from 'type-graphql';
import {Inject} from 'typescript-ioc';
import {resolverManager} from './_resolver-manager';
import {StockItem} from '../schemas';
import {StockItemModel} from '../models';
import {StockItemsMockService} from '../services';
@Resolver(of => StockItem)
  • Add the stock-items resolver to index.ts in the resolvers directory
src/resolvers/index.ts
export * from './stock-item.resolver';
  • Start the service
npm start
  • Verify that the that the resolver is available using the Graph QL browser provided by the Code Pattern
    • Open GraphQL Playground: http://localhost:3000
    • Run the query query { stockItems { name } }
- Open GraphQL Playground: `http://localhost:3000`
  • Push the changes we’ve made to the repository
git add .
git commit -m "Adds a graphql interface"
git push

Create a service implementation that calls the microservice

  • Create a folder config in following path src/config

  • Add a stock-item-service.config file in the config directory

src/config/stock-item-service.config.ts
export class StockItemServiceConfig {
baseUrl: string;
}
  • Add a stock-item-service.config.provider file in the config directory
src/config/stock-item-service.config.provider.ts
import {ObjectFactory} from 'typescript-ioc';
const baseUrl: string = process.env.SERVICE_URL || 'localhost:9080';
export const stockItemConfigFactory: ObjectFactory = () => ({
baseUrl,
});

The config class separates how the config is loaded from how it is used. In this case the config is simply retrieved from an environment variable but in more complex cases the value(s) can be retrived from external data sources.

  • Add the stock-item-service.config to an index.ts of the config directory
src/config/index.ts
import {StockItemServiceConfig} from './stock-item-service.config';
import {stockItemConfigFactory} from './stock-item-service.config.provider';
import {Container} from 'typescript-ioc';
export * from './stock-item-service.config';
Container.bind(StockItemServiceConfig).factory(stockItemConfigFactory);
  • Create a stock-items service in the services directory that uses the config
src/services/stock-items.service.ts
import {Inject} from 'typescript-ioc';
import {get, Response} from 'superagent';
import {StockItemsApi} from './stock-items.api';
import {StockItemModel} from '../models';
import {StockItemServiceConfig} from '../config';
import {LoggerApi} from '../logger';
class StockItem {
  • Add stock-item.service to index.ts in the service directory
  • Add StockItemsService instead of StockItemsMockService in the following files src/resolvers/stock-item.resolver.ts , src/controllers/stock-items.controller.ts and test/controllers/stock-items.controller.spec.ts
  • Remove stock-items-mock.service from index.ts
src/services/index.ts
export * from './stock-items.service';
  • Modify connectsTo property to the values.yaml file of the Helm chart. The value of the property should match the Kubernetes service of the microservice. (For Code Pattern projects, the service name is the same as the name of the application which is that same as the name of the repository.)
chart/base/values.yaml
...
connectsTo: inventory-management-svc-{your initials}
...

The values.yaml file of the Helm chart defines the variables that can be provided to the template as input. Now that we’ve added a new variable, we will need to update the appropriate template file to use our new variable.

  • Add a new environment variable named SERVICE_URL to the list of existing environment variables in deployment.yaml. (SERVICE_URL is the name we gave the environment variable in our stock-item-service.config class as the first step in this section.) The value of this environment variable should come from the connectsTo value we defined. You can add | quote to wrap the value in quotes in case the value is not formatted correctly.
chart/base/templates/deployment.yaml
...
env:
- name: INGRESS_HOST
value: ""
- name: PROTOCOLS
value: ""
- name: LOG_LEVEL
value: {{ .Values.logLevel | quote }}
- name: SERVICE_URL

deployment.yaml is a templatized Kubernetes yaml file that describes the deployment of our component. The deployment will create one or more pods based on the pod template defined in the deployment. Each pod that starts will have the envionment variables that we have defined in the env section available for the container image to reference.

  • Commit and push the changes to git
git add .
git commit -m "Adds service implementation"
git push