strongloop / loopback4-example-javascript

LoopBack 4 application in JavaScript
3 stars 0 forks source link

Sugar APIs for creating controllers, models, and repositories #3

Closed hacksparrow closed 2 years ago

hacksparrow commented 5 years ago

Sugar APIs for creating controllers, models, and repositories.

hacksparrow commented 5 years ago

@bajtos @raymondfeng here is the new LB4 sugar API for JS. Good thing, it preserves the underlying metadata info without exposing them to the user.

The following files are created by the user:

What makes them possible: https://github.com/strongloop/loopback4-example-javascript/tree/hack/lib

The codebase can be optimized some more. Before that, I want your feedback on whether this is the right direction or not. Do you see any potential issues, how does it look?

raymondfeng commented 5 years ago

@hacksparrow I like the idea of generating LB4 classes from plain JS. What about name these generators as *Factory?

hacksparrow commented 5 years ago

Thanks for the review, @jannyHou. It's working on my machine. Let me add some acceptance tests for everyone else to confirm.

hacksparrow commented 5 years ago

@raymondfeng good suggestion.

hacksparrow commented 5 years ago

The files under the lib dir should be published as an npm module, it will make for a much cleaner experience for the developers.

bajtos commented 5 years ago

I am proposing the following next steps:

  1. Find out how to allow JS users to define their models as shown in https://github.com/strongloop/loopback4-example-javascript/pull/3#discussion_r253058983, i.e. fix all parts of LB4 framework to support models with no design:type metadata.

  2. Look into @inject alternative for JavaScript. Ideally, we need to support the following three flavours: 1) constructor arguments 2) class properties 3) arguments of a class method. My opinion on the priorities: constructor arguments = MUST HAVE, class properties = SHOULD HAVE, method arguments = NICE TO HAVE.

For DI configuration, I'd like to consider approach based on what we have for model definition, where the additional metadata is stored in static property. For example:

class MyController {
  /** property dependencies **/
  get $propertyDependencies() {
    return {
      // inject dependencies of our parent classes
      ...this.propertyDependencies,
      // configure the property `myController.url`
      url: RestBindings.URL
    };
  }

  /** constructor dependencies **/

  constructor(currentUser) {
    this.currentUser = curentUser;
  }
  static get constructorDependencies() {
    return [AuthenticationBindings.CURRENT_USER];
  }

  /** method-level dependencies **/

  getProfile(currentUser) {
    return currentUser;
  }

  get getProfileDependencies() {
    return [AuthenticationBindings.CURRENT_USER];
  }

  /** see how it can be used for OpenAPI spec too! **/

  get getProfileSpec() {
    return { 
     responses: {
        200: {
          'application/json': {
            schema: getUserProfileSchema();
          }
        }
     }
  }

I expect my proposal to be controversial and I am happy to consider different approaches and options.

For example, we can use OpenAPI spec (OperationObject) to define method parameters to be injected. The caveat: these injected parameters must be removed from OpenAPI spec before it's returned by the app (e.g. at GET /openapi.json).

raymondfeng commented 5 years ago

Great discussion!

Here is how I see it:

  1. LB4 introduced a set of constructs/artifacts, such as model, datasource, controller, and repository. Most of them are represented as classes with some metadata. They are then bound to the Context by boot, component, or explicit API calls. With dependency injection, these building blocks can be composed to create various user experiences such as exposing REST APIs or integrating with backend data and services.

  2. We declare such constructs as TypeScript classes at the moment. Metadata are added by a few different ways:

    • Use decorators
    • Introspect TypeScript design time metadata (added by TSC)
    • Use class level properties

Most of TypeScript classes are generated/predefined. They are less magic/open than LB3 artifacts.

  1. We need to define the canonical representation of such constructs (class + metadata), which become the unified input for LB4. At this point, they are transpiled JavaScript classes from TypeScript.

  2. To entertain both Vanilla JS and TypeScript developers and minimize the gap, #3 is critical.

  3. @hacksparrow's PR tries to add factories that can generate TypeScript-flavored canonical representation. If we think that's not the best middle ground, we need to come up an agnostic format for #3. Such format should be easy to produce by APIs, and sugar/convenient alternatives (such as decoration, JSON, metadata properties by convention).

  4. We should also consider two flavors of constructs:

    • Strong-typed and fully declared classes
    • Weakly-typed and open classes (like LB3, it's also useful to generate from other forms of metadata, such as service-proxy specs - OAS, gRPC service spec, WSDL, GraphQL schema)
hacksparrow commented 5 years ago

@jannyHou, the app should work now, btw. It was caused by the hard-coded local path of the database file.