Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

From model definition to REST API with no custom repository/controller classes #2036

Closed
11 tasks done
bajtos opened this issue Nov 16, 2018 · 13 comments
Closed
11 tasks done
Labels
Boot CLI epic feature parity feature REST Issues related to @loopback/rest package and REST transport in general

Comments

@bajtos
Copy link
Member

bajtos commented Nov 16, 2018

In LoopBack 3, it was very easy to get a fully-featured CRUD REST API with very little code: a model definition describing model properties + a model configuration specifying which datasource to use.

Let's provide the same simplicity to LB4 users too.

  • User creates a model class and uses decorators to define model properties. (No change here.)
  • User declaratively defines what kind of data-access patterns to provide (CRUD, KeyValue, etc.) and what datasource to use under the hood.
  • @loopback/boot processes this configuration and registers appropriate repositories & controllers with the app.

For example, to create a Product model, attach it to a db datasources and expose a CRUD REST API, all that the developer needs is to add the following two files to their project (plus any npm packages that implement this functionality):

models/product.ts:

@model()
export class Product extends Entity {
  @property()
  name: string;
}

public-models/product.json:

{
  "model": "Product",
  "pattern": "Crud",
  "dataSource": "db",
}

Note: having config for multiple models in a single JSON file, as we do in LB 3.x, can quickly become a maintenance nightmare - see strongloop/loopback#1316. It will be better to have one config file per model.

An example file for a model using KeyValue pattern (not CRUD) - public-models/cache-entry.json:

{
  "model": "CacheEntry",
  "pattern": "KeyValue",
  "dataSource": "redis"
}

See also

Acceptance criteria

Out of scope

@bajtos bajtos added feature CLI Boot REST Issues related to @loopback/rest package and REST transport in general feature parity labels Nov 16, 2018
@bajtos bajtos changed the title From model definition to REST API without custom repository/controller classes From model definition to REST API with no repository/controller classes Nov 16, 2018
@gczobel-f5
Copy link
Contributor

Why generate a model first? I think it's better if we can put an OpenAPI YAML or JSON file on some folder, and from the definition, generate the model, controller, and repository.

@bajtos
Copy link
Member Author

bajtos commented Dec 4, 2018

Why generate a model first? I think it's better if we can put an OpenAPI YAML or JSON file on some folder, and from the definition, generate the model, controller, and repository.

Sure, that's an option too!

However, if the model is defined in a YAML/JSON file, then there are no type definitions available and as a result, the compiler cannot check types in code using the generated model & repository - typically acceptance/integration tests using JS/TS API to setup and clean the database between the tests.

@raymondfeng
Copy link
Contributor

@gczobel-f5 We already have lb4 openapi command that generates corresponding artifacts from OpenAPI specs.

To allow a set of OpenAPI specs in a folder to be discovered, we probably need to do the following:

  1. Add a booter to discover/load/code-gen for open api specs.
  2. Make it possible to invoke app.boot (up to certain stage) at build time to perform code generation.
  3. Integrate code-gen with npm run build:codegen.

@gczobel-f5
Copy link
Contributor

We already have lb4 openapi command that generates corresponding artifacts from OpenAPI specs.

@raymondfeng In my mind, I see this feature generating the model/controller/repository for simple CRUD operations in memory only, without creating any file.
If the intention is to create the files, what is the difference with the lb4 openapi importer?
This proposal #2090 is almost the same that is written here.

@bajtos
Copy link
Member Author

bajtos commented Feb 12, 2019

Cross-posting #1889 (comment)

For example, why do not provide a set of extensions in order to keep the simplicity of LB3, without the needed to create repository, controller for default CRUD actions (only if we need custom actions) ?

Yes please! It's our design goal to keep LoopBack 4+ extensible and allow the community to experiment with different styles of project layout. We don't have bandwidth to implement and maintain all such extensions ourselves, but we are happy to help anybody willing to step up and write such extension themselves.

As for repositories, it's already possible to use DefaultCrudRepository without creating per-model repository classes.

In #740, @raymondfeng has proposed an extension providing REST API via a base controller class CrudController that provides usual querying and CRUD operations, similar to what the controllers scaffolded by lb4 controller do (but without the scaffolding step).

With these two buildings blocks, I imagine one can write a small piece of code accepting a model class an exposing it via REST API, see the mock-up implementation below. It's just an illustration to show what I mean, it will probably not work out of the box and may require cleanup.

// usage - this can be automated via a custom Booter
import {Product, Category} from './models';

export class MyApplication extends RepositoryMixin(RestApplication) {
 constructor() {
    super();  
    exposeModelViaRest(app, Product);
    exposeModelViaRest(app, Category;
  }
}

// implementation
function exposeModelViaRest<T extends Entity>(
  app: ApplicationWithRepositories,
  dataSourceName: string, 
  modelCtor: Class<T>
) {
  const REPOSITORY_KEY = `repositories.${modelCtor.modelName}Repository`;
  app.bind(REPOSITORY_KEY).toDynamicValue(() => {
    const ds = await ctx.get(`datasources.${dataSourceName}`);
    return new DefaultCrudRepository(modelCtor, ds);
  });

  app.controller(createCrudControllerForModel(modelCtor, REPOSITORY_KEY));
}

function createCrudControllerForModel<T extends Entity>(
  modelCtor: class<T>,
  repositoryKey: string
) {
  // It is not possible to access closure variables like "BaseController"
  // from a dynamically defined function. The solution is to
  // create a dynamically defined factory function that accepts
  // closure variables as arguments.
  const name = modelCtor.modelName + 'Controller';
  const factory = new Function('BaseController', 'repositoryName', `
    class ${name} extends BaseController {
      constructor(repository) {
        super(repository);
      }
    }`);

  const controllerCtor = factory(CrudController, repositoryName);
  // apply @inject() decorator on the first constructor parameter
  inject(repositoryKey)(controllerCtor, undefined, 0);
  return controllerCtor;
}        

@bajtos bajtos added the 2019Q2 label Mar 1, 2019
@emonddr emonddr changed the title From model definition to REST API with no repository/controller classes From model definition to REST API with no 'visible' repository/controller classes Mar 19, 2019
@emonddr emonddr changed the title From model definition to REST API with no 'visible' repository/controller classes From model definition to REST API with no repository/controller classes Mar 19, 2019
@emonddr
Copy link
Contributor

emonddr commented Mar 19, 2019

@bajtos , we need some clarification please. The title mentions the need for "no repository or controller" classes, yet controller classes are mention a few times.

@loopback/boot processes this configuration and registers appropriate repositories & controllers with the app.
... providing CrudController class and ... providing KeyValueController class.

@dhmlau
Copy link
Member

dhmlau commented Apr 4, 2019

@bajtos, could you please confirm? Thanks.
In a separate discussion, I think you're referring this issue as "API Creation with just models - without Repository and datasource".

@bajtos
Copy link
Member Author

bajtos commented May 13, 2019

Moved this Epic from Q2 to Q3 because most of the stories (see my previous comment) are already assigned to Q3.

@bajtos bajtos changed the title From model definition to REST API with no custom repository/controller classes [EPIC] From model definition to REST API with no custom repository/controller classes Sep 12, 2019
@bajtos bajtos changed the title [EPIC] From model definition to REST API with no custom repository/controller classes From model definition to REST API with no custom repository/controller classes Sep 13, 2019
@dhmlau dhmlau added 2019Q4 and removed 2019Q3 labels Sep 17, 2019
@dhmlau
Copy link
Member

dhmlau commented Sep 17, 2019

The tasks we've targeted for 2019Q3 is done. Moving this epic to 2019Q4 for the remaining items.

@nabdelgadir
Copy link
Contributor

Blog post is ready to publish; closing this EPIC as done 👍

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Boot CLI epic feature parity feature REST Issues related to @loopback/rest package and REST transport in general
Projects
None yet
Development

No branches or pull requests

7 participants