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

Open
bajtos opened this issue Nov 16, 2018 · 12 comments

Comments

@bajtos
Copy link
Member

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

  • #1889:

    provide a set of extensions in order to keep the simplicity of LB3, without the needed to creat e repository, controller for default CRUD actions (only if we need custom actions) ?

    /cc @GuillaumeJasmin

  • #565 Declarative Support

Acceptance criteria

  • A new extension (npm package living in loopback-next monorepo) providing CrudController class. See #740 for inspiration. --> SEE #2736

  • Propose how to configure public models in such way that there is one JSON file for each model, we also need to consider how to access this file from transpiled output (dist vs src). Discuss with the wider @strongloop/loopback-next team and get an agreement. --> SEE #2738 (spike)

  • Implement TestSandbox.writeTextFile --> SEE #3731

  • Improve defineCrudRestController to create a named controller class --> SEE #3732

  • Add defineCrudRepositoryClass - a helper to create a named repository class --> SEE #3733

  • Model API booter & builder --> SEE #3736

  • Add CrudRestApiBuilder to @loopback/rest-crud --> SEE #3737

  • Example app showing CrudRestApiBuilder --> SEE #3738

  • A new CLI command that will ask the user for an existing model, an existing datasource, and then generate the corresponding public-models.json entry. Ideally, the pattern (crud vs key-value) should be automatically inferred from the datasource config (connector type). --> SEE #2739

  • Documentation for application developers (consuming this feature) and extension developers (building custom controller/repository classes) --> SEE #2740

  • Blog post - let's write it as part of this GH issue, once all other work is done, but before the Epic is called done.

Out of scope

  • A new extension providing KeyValueController class --> SEE #2737

  • Relations, see #2483

  • Hooks and customization (both levels - controller & repository). If the behavior provided by CrudController/KeyValueController and DefaultCrudRepository/DefaultKeyValueRepository is no longer enough, then developers should migrate the model from public-model.json to custom repository & controller class.

  • Exposing custom methods contributed by individual connectors on top of the default CRUD/KeyValue REST API. See #2482

@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

This comment has been minimized.

Copy link
Contributor

commented Nov 28, 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.

@bajtos

This comment has been minimized.

Copy link
Member Author

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

This comment has been minimized.

Copy link
Member

commented Dec 7, 2018

@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

This comment has been minimized.

Copy link
Contributor

commented Dec 10, 2018

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

This comment has been minimized.

Copy link
Member Author

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

This comment has been minimized.

Copy link
Contributor

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

This comment has been minimized.

Copy link
Contributor

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 bajtos changed the title From model definition to REST API with no repository/controller classes From model definition to REST API with no custom repository/controller classes Apr 11, 2019
@bajtos bajtos added the epic label Apr 12, 2019
@bajtos

This comment has been minimized.

Copy link
Member Author

commented Apr 12, 2019

The title mentions the need for "no repository or controller" classes, yet controller classes are mention a few times.

There will be controller & repository classes involved under the hood. The idea is that application developers don't have to write them, they should be created at runtime for them.

I have updated the description at the top, I hope it's more clear now.

I am also converting this task into an Epic so that we can split the work into multiple smaller tasks.

@bajtos

This comment has been minimized.

Copy link
Member Author

commented Apr 12, 2019

Created the following stories:

  • Extension: CrudRestController #2736
  • Extension: KeyValueRestController #2737
  • Spike: Booter for creating REST APIs from model files #2738
  • CLI command to expose REST API of a Model with no custom classes #2739
  • Docs for exposing REST API of a Model with no custom classes #2740
@bajtos

This comment has been minimized.

Copy link
Member Author

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.

@agnes512 agnes512 referenced this issue Jul 31, 2019
18 of 32 tasks complete
@bajtos bajtos referenced this issue Aug 23, 2019
4 of 4 tasks complete
@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

This comment has been minimized.

Copy link
Contributor

commented Sep 17, 2019

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
6 participants
You can’t perform that action at this time.