-
Notifications
You must be signed in to change notification settings - Fork 1
Conversation
Hey @virkt25, thank you for wrapping the work and sending this initial pull request. First of all, I see the project infrastructure is not fleshed out yet, e.g. there is are no I would like to make this repo public, so that I can configure Travis CI integration. Any objections?
As I am envisioning this template, it should provide a meaningful implementation/example of each entity. For example, for a custom sequence action, we can use the Common Log logger described in Thinking in LoopBack. I believe that a concrete implementation will naturally show us what tests need to be written. Let me put this into a wider context. As framework developers, we have responsibility not only to show our users how to use framework features to write extensions, but also what's the right process for building applications and extensions. I hope the necessity to have a good automated test coverage is a given thing that everyone understand these days. To focus on this starter repo, how are we going to know that our templates work as expected, when there are no tests to verify that claim? When other people start contributing changes, or even when we contribute changes in several months, how are we going to tell whether a pull request is going to break something or not, if there are no automated tests? I haven't thought about detailed testing strategy for extensions yet. At minimum, I think we should have two kinds of tests:
+1 I think some of this documentation should actually go to http://loopback.io/doc/en/lb4/Creating-components.html. IMO, READMEs and code comments should describe things specific to this starter repository only. Content relevant to all extension developers should go to our docs.
IIRC,
Hmm 🤔 Here is the use-case I have in mind: let's say we are using a microservice architecture and have multiple services accessing the same source of data - for example a weather service or perhaps an employee database. As a developer in such organization, I would like to have an extension that gives me models, repositories and datasource(s) that I can easily plug into my application to leverage this shared source of data in my microservice. To make this self-contained and independent on external services, I am proposing to leverage memory connector, configure it to load/save data to a file (see file config options). The data can be something that's usually read-only, for example a list of country codes and names? To keep the example simple, let's expose a full CrudRepository allowing consumers to modify this data too. Can we come up with a better example? Something where it makes sense to have initial seed data shared by all dependants, but where it also makes sense for each dependant to edit its copy. Perhaps some sort of a pre-populated cache?
Can you elaborate more on this please? In my mind, a controller shared via an extension would provide a common piece of REST API functionality. For example, an authentication component can provide a controller providing API for managing local users (create, edit profile, change password, etc.).
-1 I believe most components will contribute more than just one thing. For example, the authentication component is exporting few bindings (providers) and a sequence action (as a provider). I think eventually it may provide controller for local user management and more. In that case, I think it will be difficult to build such a component if one has to copy and merge multiple templates together. I am proposing to come up with a directory layout convention that works for both extensions and applications. I have already started on while working on Thinking in LoopBack - see here. In my upcoming pull request adding project infrastructure, I'll setup these subdirectories for you.
I'll admit I haven't reviewed details of your controller/mixin/repository templates yet, as I sort of got stuck with the overall project layout/setup and would like to get it right first. I think creating an abstract template makes this work unnecessary difficult, as you have already discovered when you wanted to write a unit test. IMO, we should also use this task and the starter repository as an opportunity to check the user experience of consuming different kinds of extensions (depending on what they are contributing) and make sure our design provides great UX for extension consumers (and extension authors too). I think this check is easier when we focus on specific use cases that naturally show us the limitations of our design, rather than try to address a vague/abstract set of all possible use cases. |
This Mixin starter will show you how to extend loopback-next Application OR | ||
any other class using the concept of a Mixin. A Mixin is a class which takes the | ||
input of a base class and adds / modifies existing functions / static values and returns | ||
a new class which can be instantiated. The type of returned class will still be the base class. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- Let's keep this template focused and show how to write a component mixin for
Application
. - This file should not be explaining the general concept of mixins, but point people to our documentation instead. Ideally, there should be a section in "Writing Components" explaining how to write a mixin extending application object, we should refer to that section from this file.
export interface Constructor<T> { | ||
new (...args: any[]): T; | ||
[property: string]: any; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this interface should be inherited from a shared loopback package or from @loopback/core
.
Hi @bajtos, Thanks for all the insightful comments. Since the starter template is going to be more along the lines of a completed example template, I'm thinking of doing 2 extensions. First being a A second one being a logging component that'll track parameters given to a controller operation function, and the result it produces. Or something along the lines of what is described in Thinking in Loopback ... but with the difference of everything being stored in the DB and CRUD endpoints made available for logs. -- This can be an extension along the lines of Thoughts on this? I'm running into some issues with creating a DefaultCrudRepository as specified here: https://github.com/strongloop/loopback-next/tree/master/packages/repository#define-a-repository I get the following error:
Any idea as to what I might be doing wrong? There is commented out code similar to |
Please use |
src/repositories/index.ts
Outdated
file: './data.json', | ||
}); | ||
|
||
const Count = ds.createModel<typeof juggler.PersistedModel>( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The model is defined as a regular class extending from Entity
, no datasource is involved. The DefaultCrudRepository
will create datasource-specific model under the hood.
@model()
export class Count extends Entity {
@property({type: 'string', id: true, required: true}
id: string;
// ...
}
@@ -3,6 +3,7 @@ | |||
// This file is licensed under the MIT License. | |||
// License text available at https://opensource.org/licenses/MIT | |||
|
|||
import 'mocha'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Huh? This should not be needed.
I am a bit concerned that your proposed examples are complex and require many dependencies, which may make it difficult for people using these examples as a starter template to figure out what parts are important (required for a decorator/sequence action/whatever) and what is implementation detail of our particular example. Can we come up with something simpler and more lightweight please? The starter repo should explain/show the following items:
The rest is an implementation details that we should de-emphasize, because these details will be different in each extension. As I am thinking about decorators, they are tricky. The way how they are typically used, a decorator only stores some metadata that's later used by a different piece of code. Maybe we should have two example decorators - a simple one providing a wrapper for an existing decorator to show how to expose decorators from components and test them, and a more complex one focusing more on how to implement both a decorator and the code consuming metadata from this decorator. A proposal for the simple decorator: /**
* A parameter containing the current transaction-id provided
* via HTTP request header "X-Transaction-Id".
*/
export function transactionIdHeader() {
return param.header.string('X-Transaction-Id');
} The complex decorator can build on top of your proposal, but let's simplify it: return the number of calls in the response header (instead of exposing a new HTTP endpoint, which would require a controller), and store the data using plain javascript object/Map (instead of bringing in heavy-weight juggler). I think you will need a custom sequence action in order to implement this feature too. As for the logger - the idea is to show how an extension can provide a sequence action. Again, let's keep this simple and focused on the mechanics of sequence actions. IMO, printing the logs to |
@bajtos I agree with your comments. The purpose of Let's leave the implementation as trivial as possible. For example, it's perfectly fine to use memory to keep stats. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Attempted to add a @transactionIdHeader
decorator but was unsure how to get access to request in the decorator without having it inject from Context ... which also didn't work.
Any hints / ideas / suggestions? Why can't I access context / How would I access context in a decorator?
Any ideas? In the meantime I started a new PR from scratch that implements a @log
decorator. See that for more details.
console.log('descriptor =>', descriptor); | ||
return { | ||
value: function(...args:any[]) { | ||
const req: any = Reflector.getMetadata('http.request', ) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I tried writing a simple transactionIdHeader but ran into the issue of not being able to access the Context in a decorator to get the value for the header from http.request
. Any ideas how this can be achieved? I'm guessing the right way for request associated transactions would be to do this through sequence?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here is the decorator implementation I had in mind:
export function transactionIdHeader() {
return param.header.string('transactionId');
}
It delegates the implementation complexity to the built-in @param
decorator.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To answer your original question: the decorators cannot access any context, they are invoked at the time when the source code files are required (loaded by Node.js runtime), there is no context instance created at that time yet.
You can look at @param()
implementation to understand how the param
decorator stores metadata, and parseParams()
sequence actions to understand how this metadata is used.
|
||
class MyController { | ||
@get('/') | ||
@transactionIdHeader() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I also tried injecting http.request
into transactionIdHeader() ... but that didn't work for obvious reasons.
This is no longer relevant. |
Initial PR adding templates for Mixins, Controllers, Repositories.
Need to do:
Questions
@bajtos Am I on the right track? I've been trying to create something abstract since it's meant to be a template to start from. Thoughts / comments / changes appreciated :)
connect to loopbackio/loopback-next#525