docs/spikes/2020-04-how-to-migrate-lb3-components.md
GitHub issue: https://github.com/loopbackio/loopback-next/issues/4099
Migration of the following component is out of scope of this spike:
This section provides a list of techniques compiled from existing LoopBack 3 components. Please check Overview of existing LB3 components below if you are interested in learning more about where the techniques are coming from.
Expected component module layout and exports. How to structure the extension code to receive user-provided configuration & target app instance. What are the instructions for adding a LB4 component to a LB4 app.
A context shared by all parts of the application, allowing different layers
to store and retrieve values like "the current user". In LB3, we have
loopback-context based on continuation-local-storage and options-based
context propagation.
How to migrate a component exporting mixins. See Migrating model mixins for migration guide for app developers (mixin consumers).
Contribute custom entities (Application, Installation) to be persisted via
CRUD, exposed via REST and possibly further customized by the app.
Customization options include which datasource to use, the base path where
REST API is exposed (e.g. /api/apps and /api/devices), additional fields
(e.g. Application.tenantId) and changes in persistence behavior (e.g. via
Operation Hooks).
Contribute custom models (Notification) describing shape of data expected by the services (Push service). These models are not backed by any datasource, they are primarily used to describe data fields.
Add a custom Operation Hook to given models, with a config option to enable/disable this feature. The list of models can be provided explicitly in the component configuration or obtained dynamically via introspection (e.g. all models having a "belongsTo" relation with the Group model)
Add a custom Operation Hook to all app models matching given criteria (have a "belongsTo" relation with the Group model, are listed in the component config), plus a config option to enable/disable this feature.
Add new relations, e.g. between an app-provided entity User and a
component-provided entity File. In this variant, the relation is added on
small fixed number of models.
A model mixing adding new relations (hasMany ModelEvents), installing
Operation Hooks (to generate model events/audit log entries), adding new
Repository APIs (for working with related model events).
(The mixin-based design may be difficult to accomplish in LB4, we may want to use introspection and a model setting instead. The trickier part is how to apply changes to models added after the component was mounted.)
For all models with a flag enabled in model settings, setup a custom
afterRemote hook to modify the HTTP response (e.g. add additional
headers).
Add a new REST API endpoint to every (public) model/entity provided by the
app, e.g. GET /api/{model}/schema returning JSON schema of the model.
Add a new REST API endpoint at a configured path, e.g. /visualize
returning an HTML page.
REST API endpoints providing file upload & download.
Add a new local service (e.g. Ping.ping()) and expose it via REST API
(e.g. /ping), allow the user to customize the base path where the API is
exposed at (e.g. /pong).
Intercept REST requests handled by an endpoint contributed by a 3rd-party controller to invoke additional logic before the controller method is called (e.g. automatically create containers when uploading files) and after the controller method returned (e.g. post-process the uploaded file).
Add new REST API endpoints using Express (req, res, next) style.
Add a new local service (a class providing JS/TS API), e.g. Push service or (database) Migration service.
The service must be configurable by the target app (e.g. Apple & Google credentials for push notifications, transport config for logger frameworks, Amazon S3 credentials for storage component, etc.).
LB3 typically uses component configuration. I think in LB4, we can use
@configure specific to each service (see the logger extension for an
example).
Inject LB3 models (LB4 entities & repositories) to the service. These models can be provided either by the extension or by the target application. When using models from the target application, the developer needs an option to specify which models (entities) and associated repositories to use.
Note: In LB4, models/entities are used primarily for the type information
(to describe the shape of model data). As part of the follow-up research, we
will need to decide if it's enough to inject LB4 Repository to a component
or if we need to inject model/entity classes too. Also note that
DefaultCrudReposiory class provides a public instance property
entityClass referencing the model/entity it's bound to.
Provide a service factory to create services dynamically at runtime, e.g.
named child loggers created via app.log('name') in LB3.
Provide a custom service connector requiring (environment-specific) configuration and providing a static service API (static means the API is always the same, as opposed to API generated dynamically from WSDL or OpenAPI).
A new server type to handle requests coming from a non-REST source, e.g. Primus/WebSockets and RabbitMQ.
WebSockets: mount WebSocket-handler on the underlying http server used by
@loopback/rest.
Message queues: allow consumers to also produce (publish) new messages, e.g. as a response to the incoming message.
Add a custom step to the default LB3 authentication/authorization workflow, so that we can compute additional information about the current user (e.g. groups the user belongs to).
Note: I think this step could be replaced by lazy initialization, where the current user groups are computed on the first access and cached for subsequent use.
A new role resolver (e.g. using groups), the app developer needs a way how to configure which models/repositories to use for role resolution.
Get a list of all models (entity classes) used by the application
Get definition of a given model (information about properties, relations, mixins, etc.)
Get a list of all remote methods (REST API endpoints) provided by a given model.
Obtain a list of all REST endpoints provided by the application, including metadata about request parameters & response schema, and any other information necessary to configure reverse-proxy routing rules (Kong, nginx, etc.).
Component layout & instructions for mounting to an app
See Migrating component project layout.
Migrate context
See Migrating access to current context.
Migrate mixins
See Migrating components contributing Model mixins
Contribute custom models (Notification) describing shape of data expected by the services (Push service). These models are not backed by any datasource, they are primarily used to describe data fields.
See Migrate behavior-less models.
Contribute custom entities (Application, Installation) to be persisted via CRUD, exposed via REST and possibly further customized by the app. Customization options include which datasource to use, the base path where REST API is exposed (e.g.
/api/appsand/api/devices), additional fields (e.g.Application.tenantId) and changes in persistence behavior (e.g. via Operation Hooks)
Add a custom Operation Hook to given models, with a config option to enable/disable this feature. The list of models can be provided explicitly in the component configuration or obtained dynamically via introspection (e.g. all models having a "belongsTo" relation with the Group model)
Add new relations, e.g. between an app-provided entity
Userand a component-provided entityFile. In this variant, the relation is added on small fixed number of models.
A model mixing adding new relations (
hasMany ModelEvents), installing Operation Hooks (to generate model events/audit log entries), adding new Repository APIs (for working with related model events).(The mixin-based design may be difficult to accomplish in LB4, we may want to use introspection and a model setting instead. The trickier part is how to apply changes to models added after the component was mounted.)
For all models with a flag enabled in model settings, setup a custom
afterRemotehook to modify the HTTP response (e.g. add additional headers).
See Extending REST API endpoints for instructions how to write components contributing REST API endpoints.
See Migrating components: REST API endpoints for migration steps.
Add a new REST API endpoint to every (public) model/entity provided by the app, e.g.
GET /api/{model}/schemareturning JSON schema of the model.
First, we need to find all publicly exposed models. This is tricky in LB4,
because there is no direct mapping between models and REST API endpoints. REST
APIs are implemented by Controllers, there may be more than one Controller for
each Model (e.g. ProductController and ProductCategoryController).
I am arguing that the particular use case of accessing JSON schema of a model is not relevant in LB4, because model schema can be obtained from the OpenAPI spec document provided by all LB4 applications out of the box.
Let's not provide any migration guide for this technique for now. We can wait until there is user demand and then build a better understanding of user requirements before looking for LB4 solution.
Intercept REST requests handled by an endpoint contributed by a 3rd-party controller to invoke additional logic before the controller method is called (e.g. automatically create containers when uploading files) and after the controller method returned (e.g. post-process the uploaded file).
Considering that this use case is specific to the way how loopback-component-storage implements REST API endpoints for file uploads and downloads, I feel it's not necessary to provide migration guide for this case.
Created a follow-up epic loopback-next#5424 to look into this area later, when there is user demand.
Created a follow-up epic loopback-next#5425 to look into this area later, when there is user demand.
Considering the huge difference between the auth & auth design in LB3 and LB4, I think it's not feasible to migrate existing LB3 auth extension(s) to in way that will preserve the high-level design. Instead, users need to approach the auth layer with a fresh perspective and structure their extensions around the new architecture of auth & auth.
In that light, I don't see much value in describing how to migrate techniques from LB3 auth extensions to LB4, because such techniques are very likely to be irrelevant in LB4 world.
Let's see what questions LoopBack 4 users come with and write content that's useful to current LB4 applications & extensions, instead of documenting techniques that used to be useful in the past.
Add a custom step to the default LB3 authentication/authorization workflow, so that we can compute additional information about the current user (e.g. groups the user belongs to).
Note: I think this step could be replaced by lazy initialization, where the current user groups are computed on the first access and cached for subsequent use.
A new role resolver (e.g. using groups), the app developer needs a way how to configure which models/repositories to use for role resolution.
Created a follow-up epic loopback-next#5426 to look into this area later, when there is user demand.
This is not a typical LoopBack component because it's not configured via
component-config.json. Instead, the component provides a connector to be
configured via server/datasources.json and server/config.json, plus a set of
(base) models that are expected to be added to the target application.
Models:
application - Holds app-specific push settings like Apple and Google
credentials, this model is typically persisted in the "main" database. A
single LoopBack project (application) can host multiple push-notification
client application instances.
installation - Represents a device registered for receiving push
notifications, it's typically persisted in the "main" database. Client
applications (iOS, Android) are consuming REST APIs of this model.
notification - describes the shape (data fields) of a single notification to
be sent. This is essentially a plain Model that does not need to be attached
to any datasource.
push - a service-like model to hold methods for sending notifications, e.g.
notifyById, it's attached to the datasource using the push component as the
connector.
Contribute custom entities (Application, Installation) to be persisted via
CRUD, exposed via REST and possibly further customized by the app.
Customization options include base path where REST API is exposed (e.g.
/api/apps and /api/devices), additional fields (e.g.
Application.tenantId) and changes in persistence behavior (e.g. via
Operation Hooks).
Contribute custom behavior (Push service) requiring user-provided configuration (Apple & Google credentials, dependant models/repositories to use).
Contribute custom model (Notification) to describe shape of data expected by the services (Push service).
Again, this is not a typical LB3 component.
server/datasources.json.Models:
Container is a service-like model that should be attached to the storage
datasource and provides JS & REST APIs for working with both containers and
files.The concept of a Container (groups files, similar to a directory or folder) and a File (stores the data, such as a document or image) is implicit, there are no LB3 models to formally describe these entities.
Contribute a new service, it requires user-provided configuration (Amazon S3/IBM COS credentials).
Contribute a REST API for this new service, allow the user to customize the
base path where the API is exposed at (e.g. /api/containers).
REST API implements endpoints for file upload & download.
NOTE: We already have a Winston-based logging extension, see @loopback/logging.
https://www.npmjs.com/package/loopback-component-bunyan
Creates a Bunyan logger based on configuration in component-config.json file.
This component contributes:
app.logger() factory function for creating named loggersIt uses the configuration provided in server/component-config.json to
configure the underlying Bunyan logging library (most notably the transports).
const log = app.logger('MyComponent');
log.debug({}, 'My log message');
https://www.npmjs.com/package/loopback-component-winston
Creates winston logger based on configuration in component-config.json file.
This component contributes:
app.log() function for producing logs, the method name can be customized.It uses the configuration provided in server/component-config.json to
configure the underlying Winston logging library (most notably the transports).
https://www.npmjs.com/package/loopback-component-access-groups
This loopback component enables you to add multi-tenant style access controls to a loopback application. It enables you to restrict access to model data based on a user's roles within a specific context.
This component is rather complex.
The component is heavily depending on loopback-context, which uses continuation-local-storage (CLS) to provide per-request context storage. Note that CLS is not reliable, it can loose context e.g. when connection pools are involved.
It defines a custom strong-remoting phase executed after regular authorization but before method invocation, this phase adds extra properties to strong-remoting context.
Applications are expected to provide two additional models Group and
GroupAccess, names of these models can be customized. Application models
(e.g. Product and Category) are expected to have a belongsTo Group
relation set up to enable group-based access control.
The component installs a new role resolver that uses group membership to resolve user roles.
It also provides some middleware:
userContextMiddleware to set the current user & their groups in the
CLS-based contextaccessLoggerMiddleware to log HTTP requests with additional information.Optionally, it installs an access Operation Hook to all models that have a
belongsTo relation to Group model, this hook modifies filter and
where queries to limit the results only to model instances allowed by
access control settings.
There is a partial example app showing the component in practice, see https://github.com/fullcube/loopback-example-advanced-access-control
A context shared by all parts of the application, allowing different layers to store and retrieve values like "the current user".
A custom step added to the default authentication/authorization sequence, so that we can compute additional information about the current user (e.g. groups the user belongs to).
Note: I think this step could be replaced by lazy initialization, where the current user groups are computed on the first access and cached for subsequent use.
A new role resolver, the app developer needs a way how to provide custom models/repositories to use for role resolution.
A custom Operation Hook applied to all app models matching given criteria (have a "belongsTo" relation with the Group model), plus a config option to enable/disable this feature.
https://www.npmjs.com/package/loopback-component-braintree
The Braintree connector for the LoopBack framework
AFAICT, this is a typically Service Proxy connector, similar to our SOAP/REST connectors.
https://www.npmjs.com/package/@oneflow/loopback-component-primus
Primus adapter for loopback. It allows you to call loopback's remote methods via websocket.
https://www.npmjs.com/package/loopback-component-rabbitmq
This component provides a convenient way to work with RabbitMQ within a loopback application. This includes:
- Defining a RabbitMQ topology using the component-config.json
- Registering message producers and consumers handlers using a mixin.
- Inspecting RabbitMQ stats and queue statuses using a RabbitMQ loopback model.
In addition, an optional mixin is provided that provides an easy way to attach message producer and consumer helper methods directly to your loopback models.
@loopback/rest.https://www.npmjs.com/package/loopback-component-meta
Component for LoopBack that adds a Meta model that can be used to retrieve meta data about the model definitions.
https://www.npmjs.com/package/loopback-jsonschema-generator
Generates JSON schemas for your LoopBack models Access the generated JSON schema url http://yourapi.com/api/{model-plural-name}/json-schema
https://www.npmjs.com/package/loopback-component-visualizer
Visualizing a model is sometimes a difficult task. When the data model gets larger, it becomes even more difficult to understand how models relate to each other.
loopback-component-visualizer helps you in creating a model diagram with a representation of all the properties, methods and relationships of your models for your loopback application.
/visualize)https://www.npmjs.com/package/loopback-component-migrate
A library to add simple database migration support to loopback projects.
Migrations that have been run will be stored in a table called 'Migrations'. The library will read the loopback datasources.json files based on the NODE_ENV environment variable just like loopback does. The usage is based on the node-db-migrate project.
https://www.npmjs.com/package/loopback-component-ping
Component for LoopBack that adds a model for retrieving the internal state of the Node process.
It is a wrapper around the express-ping package.
https://www.npmjs.com/package/component-storage
This loopback storage component is an extension to loopback-component-storage, it uses 3rd party image processing library Sharp for recreating images.
https://www.npmjs.com/package/loopback-component-changestreamer
The component observes a number specified models and notifies about the changes by SSE.
The main difference with Loopback /change-stream channels is that this implementation creates only two observers (after save and after delete) per model and then streams the changes to keep-alive registered connections. In contrast Loopback creates two same observers for each connection.
https://www.npmjs.com/package/@aliatech/loopback-component-traceability
Module for Loopback Framework that allows to keep a persisted traceability of custom operations over models
https://www.npmjs.com/package/@joinbox/loopback-component-remote-microservice
Loopback component to expose and consume models of remote microservices.
This component is too complex to understand, I am leaving it out of the initial version of the migration guide.
https://www.npmjs.com/package/loopback-component-kong-sync
This is a Loopback JS component to synchronize routes to Kong API Gateway.
This component map all Models and Methods from Loopback standardizing it and creating the related routes in Kong.
https://www.npmjs.com/package/@joinbox/loopback-component-custom-headers
Sets cache headers according to the model configuration
Migration.migrateTo.Ping.ping()) and expose it via REST API (e.g.
/ping).User and a
component-provided entity File.(req, res, next) style.hasMany ModelEvents), installing
Operation Hooks (to generate model events/audit log entries), adding new
Repository APIs (for working with related model events).afterRemote hook to modify the HTTP response (e.g. add additional
headers).Migration of mixin implementation is already covered by Migrating model mixins .
However, we are missing instructions for migrating a component exporting mixins and apps consuming mixins from 3rd party components.
Let's use the most popular mixin loopback-ds-timestamp-mixin as an example component to drive the migration docs.