-
Notifications
You must be signed in to change notification settings - Fork 2
feature(main): Basic mixin implementation #1
Conversation
src/typeorm-repository-mixin.ts
Outdated
// rules in TypeScript. | ||
const options = args[0] as ConnectionOptions; | ||
// XXX(kjdelisle): Add logging in case this fails? | ||
this.connection = this.connectionManager.create(options); |
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.
Is only one connection is allowed for the whole app?
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.
If this module ends up becoming something we want to keep, we can make it more robust.
For me the big question is "do we want to mount all of our ORMs like this, or is there a better way?"
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.
There are a few different ways to mount an ORM:
- Just use a mixin to add sugar methods to
app
and you are on your own. - Define a bunch of unified
Repository
interfaces in LoopBack and have the ORM implements an adapter to fit in.
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 took a quick dip in the typeorm source and it seems like they let the drivers handle things like application pooling on a case-by-case basis: https://github.com/typeorm/typeorm/blob/master/src/connection/Connection.ts#L149-L152
It might be that their ConnectionManager is for handling disparate connections between datasources, and that each Connection represents an active session with a datasource that may be a connection, connection pool, or even a facade for something else?
b64b0fb
to
c5d04bb
Compare
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 proposal looks reasonable to me. I am concerned about the amount of global per-application state we have.
- See my comment below for global registry instances.
- Right now, you allow the app to connect to a single database only. IMO this is too limiting - what if the app developer needs to connect to a legacy db2 database to access employee data and to a more recently added MySQL database to access customer relations data, so that they can provide a single REST API shielding the API clients from the complexity of company's systems of record? IIUC, this is handled by the concept of a datasource in legacy juggler, where each datasource represents a different database (or a backend service).
src/typeorm-repository-mixin.ts
Outdated
*/ | ||
typeOrmRepository<S>(ctor: Constructor<S>): Binding { | ||
const repo = this.connection.getRepository<S>(ctor); | ||
return this.bind(`repositories.${ctor.name}`).to(repo); |
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.
IIUC, this creates a single Repository instance that's shared across all incoming requests. Shouldn't we create local per-request repository instances?
Out of curiosity, have you tried to add support for transactions? It makes me wonder how easy/difficult that would be. See https://github.com/typeorm/typeorm/blob/master/docs/transactions.md
@kjdelisle is there any particular reason for keeping this work private, hidden from LoopBack Next community collaborators? |
Technically, I wanted to keep this module for myself as my own open source contribution, but I migrated it to the org to make it easier for the team to review. I'd like to take it back if I can. :) I am planning on reworking the way connections are handled (using the ConnectionManager), but I'm currently reworking the tests to use Dockerode like we do in some of our other connectors and once I've got that working, I'll have a free hand to make other changes. |
c5d04bb
to
9d48df6
Compare
I think I've got the Dockerode setup done, so I should be able to run tests in Jenkins now. Going to work on refactoring the ConnectionManager now. |
9d48df6
to
885e6de
Compare
I've now refactored it to the point where you can define your own connection instances. In the acceptance test, I create two repositories on separate connections to demonstrate that it works. I can't get this to work with a Provider because I need the constructor for the entity to generate the repository instance. This means that anyone who wants to have their Repository instances provided in this way would have to make a unique provider for each Repository type, make a unique binding on the application context for each unique provider and then inject their entity constructor for use there.
Looking at the syntax for the |
README.md
Outdated
# This will pull and setup a mysql instance to run acceptance tests against. | ||
source setup.sh | ||
# This runs install and test one after the other. | ||
npm it |
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.
👍
constructor(...args: any[]) { | ||
super(...args); | ||
this.typeOrmConnectionManager = new TypeORMConnectionManager(); | ||
this.bind('typeorm.connections.manager').to( |
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.
Can we use this.bind('typeorm.connections.manager').toClass(TypeORMConnectionManager)
here?
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.
This is meant to be a single, pre-instantiated instance. There's no reason to do that since it would require me to re-inject it back into the Application constructor just to make use of it (which is silly).
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.
Oh geez. Overlooked the line above. Makes sense!
it('can create entities', async () => { | ||
const customer = getCustomer(); | ||
const repo = (await app.get( | ||
`repositories.${Customer.name}`, |
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.
repositories.${customer.name}
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.
It's the class name, Customer
. It won't work with your recommendation.
885e6de
to
9696903
Compare
This PR will hopefully demonstrate what I consider to be a better practice for adding any ORMs to loopback4.
This should also mean that the legacy-juggler, and any possible future "juggler"/in-house ORMs would follow a pattern similar to this, so that all ORMs receive the same treatment with respect to API compatibility.
connected to loopbackio/loopback-next#537
Ideally, we'll be able to take this approach and enhance it, or learn from it to make an even better pattern for ORM wireup in loopback4.