deepkit / deepkit-framework

A new full-featured and high-performance TypeScript framework
https://deepkit.io/
MIT License
3.2k stars 123 forks source link

deepkit/orm: Support schema-level inheritence (discriminators) #83

Closed Rush closed 3 years ago

Rush commented 3 years ago

Prior work: https://mongoosejs.com/docs/discriminators.html

Discriminators are a schema inheritance mechanism. They enable you to have multiple models with overlapping schemas on top of the same underlying MongoDB collection.

Suppose you wanted to track different types of events in a single collection. Every event will have a timestamp, but events that represent clicked links should have a URL. You can achieve this using the model.discriminator() function. This function takes 3 parameters, a model name, a discriminator schema and an optional key (defaults to the model name). It returns a model whose schema is the union of the base schema and the discriminator schema.

Taken from a conversation on the Deepkit community Slack

What I wrote on Slack:

Damian Kaczmarek 2:43 PM the rationale for this is having 90% of logic that's the same between these "entities" and the 10% being different. For example imagine a graphical object that can be generated from different source data. All of those objects have exact same business logic but different representation in terms of their source data.

also in my code base I want these extensions of the base entity done as modules so can't have everything shoved into the base schema :slightly_smiling_face:

In typescript language I imagine this to be ...

class Drawing {
type: string;
dimensions: { w: number, h: number; }
}
class VectorDrawing {
type: 'VectorDrawing';
svg: string;
}
class RasterDrawing {
type: 'RasterDrawing';
rasterSource: 'https://s3....',
mimetype: 'image/png',
}

in deepkit code

@Entity('Drawing')
class Drawing {
  @t
  type: string;
  @t
  dimensions: { w: number, h: number; }
}
@Entity('VectorDrawing')
class VectorDrawing extends Drawing {
  @t.literal('VectorDrawing').discriminant
  type: 'VectorDrawing';
  @t
  svg: string;
}
@Entity('RasterDrawing')
class RasterDrawing extends Drawing {
  @t.literal('RasterDrawing').discriminant
  type: 'RasterDrawing';
  @t
  rasterSource: 'https://s3....',
  @t
  mimetype: 'image/png',
}

Marc 2:51 PM Ah I see, well no that's currently not implemented yet, but definitely will be. In mongo its relatively easy to implement, but for > sql databases it's a little bit different (however we need support for that feature for all supported databases)

Damian Kaczmarek 2:51 PM so Drawing shouldn't know anything about Raster/Vector 2:52 so that when you add a new type of drawing the base one doesn't have to be modified

Marc 2:55 PM Absolutely necessary feature, indeed. I think it's already possible to get this working, but that would require some custom query code. Question is if it should be possible to query the whole table (in contrast to only query one particular entity), and get a list of all different types returned at once

Marc 2:57 PM the latter would be pretty tricky with the query builder API. Don't have a use-case either at hand, was just curious if mongoose supports that

Some design considerations

Using the example above:

Rush commented 3 years ago

Updated example:

@entity.name('Drawing')
class Drawing {
  @t
  dimensions?: { w: number, h: number; }

  constructor(@t public type: string) {}
}

@entity.name('VectorDrawing', { extends: Drawing })
class VectorDrawing extends Drawing {
  @t.literal('VectorDrawing').discriminant
  type!: 'VectorDrawing';

  constructor(@t public svg: string) {
      super('VectorDrawing');
  }
}

@entity.name('RasterDrawing',  { extends: Drawing })
class RasterDrawing extends Drawing {
  @t.literal('RasterDrawing').discriminant
  type!: 'RasterDrawing';

  constructor(@t public rasterSource: string, @t public mimetype: string) {
      super('RasterDrawing');
  }
}
marcj commented 3 years ago

I'm going to close this since that feature was implemented.