-
Notifications
You must be signed in to change notification settings - Fork 1.1k
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
[DO NOT MERGE] Add core classes for data modeling #62
Conversation
The concept is LGTM. |
packages/juggler/lib/mixin.ts
Outdated
/** | ||
* A constructor | ||
*/ | ||
export type Constructable = new (...args: any[]) => Object; |
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's don't return a generic Object
, they return a specific class instance.
I am proposing to use the following definition from TypeScript's wiki:
type Constructor<T> = new(...args: any[]) => T;
I would personally prefer design patterns from TypeScript team over our own inventions, I believe they are less likely to contain subtle flaws and should be easier to understand for new people (TypeScript developers) discovering LoopBack code base.
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 would personally prefer design patterns from TypeScript team over our own inventions
👍
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 that but it gives me quite a bit trouble. One thing I cannot do is Constructor<?>
which means a unknown type of constructor.
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.
Did you try Constructor<any>
or Constructor<Object>
? I think the latter should be the same as what you have right now.
mixin<BC extends Constructable>(Base: BC): BC; My concern is that The example provided on TS wiki uses an anonymous class as the return type. function Tagged<T extends Constructor<{}>>(Base: T) {
return class extends Base {
_tag: string;
constructor(...args: any[]) {
super(...args);
this._tag = "";
}
}
} This has the unfortunate effect that typescript compiler is reporting interface Tagged {
_tag: string;
}
interface WithTag {
new(...args: any[]): Tagged;
}
function Tagged<T extends Constructor<{}>>(Base: T): T & WithTag {
return class extends Base implements Tagged {
_tag: string;
constructor(...args: any[]) {
super(...args);
this._tag = '';
}
};
}
const TaggedPoint = Tagged(Point);
// VSCode shows the following hints:
// [tslint] variable name must be in camelcase or uppercase (variable-name)
// const TaggedPoint: typeof Point & WithTag Based on that, I think the export interface MixinProvider<Mixin> {
mixin<BC extends Constructor<BC>>(Base: BC): BC & Mixin;
}
export interface MixinFunc<Mixin> {
<BC extends Constructor<BC>(Base: BC): BC & Mixin;
}
// usage
const fn: MixinFunc<WithTag> = Tagged; Thoughts? |
packages/juggler/lib/mixin.ts
Outdated
* Mix in properties/methods into a base class | ||
* @param Base The base class | ||
*/ | ||
mixin<BC extends Constructable>(Base: BC): BC; |
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.
See #62 (comment)
packages/juggler/lib/model.ts
Outdated
* See https://en.wikipedia.org/wiki/Domain-driven_design#Building_blocks | ||
*/ | ||
|
||
export abstract class Model { |
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.
abstract class
or interface
- what are the arguments to choose one over another?
As I has shown in my spike #54, TypeScript's type system compares classes (constructors) by reference, while interfaces are compared by content (member signatures).
What if we defined ModelMixin
that would add these three new members? That would allow us to convert any TypeScript/JavaScript class (e.g. coming from a 3rd party package) into a model.
Thoughts?
packages/juggler/lib/model.ts
Outdated
/** | ||
* Base class for entities which have unique ids | ||
*/ | ||
export abstract class Entity { |
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 it desirable to have a class that's both a Model
and an Entity
at the same time? How are we going to achieve that, considering that JavaScript does not support multi-inheritance?
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.
Entity
should extend from Model
@raymondfeng Good start! I reviewed the code, I find it difficult to understand the design in all details without seeing it in action. I think we really need to add a simple connector implementation and few unit tests to verify that all pieces fit together as envisioned. I am almost sure there are places that will need some tweaking to make them work. |
There is one design decision that may or may not be subjective.
I don't know which choice is the best, but I think we should make careful consideration befor picking one. What I see as benefits of converting model instances into data objects in
|
@@ -0,0 +1,8 @@ | |||
import {ValueObject} from "../../lib/model"; |
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.
Nit:
We should be consistent in import
... eg:
import {ValueObject} from "juggler" // or in a real app "@loopback/juggler"
@model() | ||
class Order extends Entity { | ||
@property({name: 'qty', mysql: { | ||
column: 'QTY' |
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.
so clean... 👍
id: string; | ||
customerId: string; | ||
|
||
@belongsTo() |
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 there some prior art for this? this doesn't really read right to me...
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.
import {property, model, belongsTo} from "../../lib/decorator"; | ||
import {Customer} from './customer' | ||
|
||
@model() |
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 there isn't an argument to the decorator - can't this be @model
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.
@model()
can optionally accept an object as the argument to spell out more metadata.
packages/juggler/lib/dao.ts
Outdated
|
||
import {MixinBuilder, Constructable} from './mixin'; | ||
|
||
module.exports = function (ds: DataSource, Model: Constructable) { |
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 this just for compatability with juggler? I find this code below incredibly confusing and obscure...
I'm not even sure where this is used...? Is there example somewhere?
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'll add more comments in the code.
@raymondfeng can you start by creating a real world or close to real world example where you aren't the one defining the requirements? Eg. take a real set of data models that aren't defined by you and create an example of how you would create them in LB-N? That way we can really make sure that our design is going the right way. |
ccc8786
to
360cf01
Compare
Closing for now, open again when the time comes. |
The purpose of this PR is to facilitate the on-going reviews/feedbacks. Let's keep it open but not merge it into master yet. |
3b8ef67
to
0ca8bce
Compare
I'll add more tests to show case the full usage. |
I don't think its a matter of adding more tests - we need real world examples to validate our design |
2d7102a
to
b1e9e62
Compare
0df68a1
to
d396ba6
Compare
Hey @raymondfeng - can you re-open this when it comes back up on your plate? I want to keep the PR list actionable - so we can use it as a sort of TODO list for review / etc. |
cc @bajtos @raymondfeng @ritch @superkhau
WIP to add core classes for data modeling & persistence