Skip to content

Commit

Permalink
Refactor repositories to add new EventRepository abstract class
Browse files Browse the repository at this point in the history
  • Loading branch information
khaosdoctor committed Nov 26, 2018
1 parent 12f8dab commit 0f1586f
Show file tree
Hide file tree
Showing 6 changed files with 101 additions and 18 deletions.
59 changes: 59 additions & 0 deletions README.md
Expand Up @@ -28,6 +28,8 @@
- [Sessions](#sessions)
- [Interfaces](#interfaces)
- [IPaginatedQueryResult](#ipaginatedqueryresult)
- [IEntityConstructor](#ientityconstructor)
- [My repository is not included, what do I do?](#my-repository-is-not-included-what-do-i-do)

## Instalation

Expand Down Expand Up @@ -365,3 +367,60 @@ interface IPaginatedQueryResult<TDocument> { // TDocument is the type that repre
total: number // Query total
}
```

### IEntityConstructor

Represents the constructor of an entity

```ts
interface IEntityConstructor<Entity> {
new(events?: IEvent<any>[]): Entity
}
```

## My repository is not included, what do I do?

Since this lib is open source and generic enough to be used by multiple repositories, there's no way to know which repositories the users are going to be using. So we added a way for you to create your own.

In order to create a repository, your class **must** extend the `EventRepository` class, which is fully abstract and is as follows:

```ts
export interface IEntityConstructor<Entity> {
new(events?: IEvent<any>[]): Entity
}

export abstract class EventRepository<TEntity extends IEventEntity> {

protected readonly _Entity: IEntityConstructor<TEntity>

constructor (Entity: IEntityConstructor<TEntity>) {
this._Entity = Entity
}

abstract async save (entity: TEntity): Promise<TEntity>

abstract async findById (id: any): Promise<TEntity | null>

abstract async runPaginatedQuery (query: { [key: string]: any }, page: number, size: number, sort: { [field: string]: 1 | -1 }): Promise<IPaginatedQueryResult<{ events: IEvent<TEntity>[] }>>
}
```

In order to maintain consistency between implementations, the following methods **must** be implemented:

- `save`: Should save the given entity to the database and return the entity
- `findById`: Should find an entity by its ID in the database. It is important to notice that, once found, the returned value should be a newly created instance of that entity (this is where you're going to use the `setPersistedEvents` method)
- `runPaginatedQuery`: Should return a paginated query from the database

Besides these methods, any class that extends `EventRepository` will inherit the `_Entity` property, which refers to the entity constructor. This will be used when returning the newly created entity from the database during the `findById` method and seting its persisted events on the newly instantiated class, like so:

```ts
async function findById (id) {
/* finds the data */
const instance = this._Entity() // Creates a new instance of <Entity>
return instance.setPersistedEvents(yourEvents) // Sets the returned data into the instance
}
```

Those are the required implementations, any additional functionalities you'd like to include in the repository can be added at will.

> For further explanation and examples, refer to the [MongodbEventRepository file in the `src` folder](./src/classes/repositories/MongodbEventRepository.ts)
22 changes: 22 additions & 0 deletions src/classes/repositories/EventRepository.ts
@@ -0,0 +1,22 @@
import { IEvent } from '@nxcd/tardis'
import { IEventEntity } from '../../interfaces/IEventEntity'
import { IPaginatedQueryResult } from '../../interfaces/IPaginatedQueryResult'

export interface IEntityConstructor<Entity> {
new(events?: IEvent<any>[]): Entity
}

export abstract class EventRepository<TEntity extends IEventEntity> {

protected readonly _Entity: IEntityConstructor<TEntity>

constructor (Entity: IEntityConstructor<TEntity>) {
this._Entity = Entity
}

abstract async save (entity: TEntity): Promise<TEntity>

abstract async findById (id: any): Promise<TEntity | null>

abstract async runPaginatedQuery (query: { [key: string]: any }, page: number, size: number, sort: { [field: string]: 1 | -1 }): Promise<IPaginatedQueryResult<{ events: IEvent<TEntity>[] }>>
}
28 changes: 17 additions & 11 deletions src/classes/repositories/MongodbEventRepository.ts
@@ -1,27 +1,28 @@
import { IEvent } from '@nxcd/tardis'
import { Collection, ObjectId, ClientSession } from 'mongodb'
import { EventRepository, IEntityConstructor } from './EventRepository'
import { IEventEntity } from '../../interfaces/IEventEntity'
import { IEventRepository } from '../../interfaces/IEventRepository'
import { Collection, ObjectId, ClientSession } from 'mongodb'
import { IPaginatedQueryResult } from '../../interfaces/IPaginatedQueryResult'

interface IDatabaseDocument {
state: any
events: IEvent<any>[]
}

interface Constructor<Entity> {
new(events?: IEvent<any>[]): Entity
}

export abstract class MongodbEventRepository<TEntity extends IEventEntity> implements IEventRepository<TEntity> {
export abstract class MongodbEventRepository<TEntity extends IEventEntity> extends EventRepository<TEntity> {
protected _collection: Collection
private _Entity: Constructor<TEntity>

constructor (collection: Collection, Entity: Constructor<TEntity>) {
constructor (collection: Collection, Entity: IEntityConstructor<TEntity>) {
super(Entity)
this._collection = collection
this._Entity = Entity
}

/**
* Tries to execute function using given session
* @param {Function} fn Function to be executed
* @param {ClientSession} session MongoDB user session
* @returns {*} Function result
*/
protected async _tryRunningWithSession (fn: Function, session: ClientSession) {
session.startTransaction()
try {
Expand Down Expand Up @@ -76,7 +77,7 @@ export abstract class MongodbEventRepository<TEntity extends IEventEntity> imple
* @param {{[field: string]: 1|-1}} sort Fields to sort
* @returns {Promise} Set of results
*/
protected async _runPaginatedQuery (query: { [key: string]: any }, page: number, size: number, sort: { [field: string]: 1 | -1 } = {}): Promise<IPaginatedQueryResult<{ events: IEvent<TEntity>[] }>> {
async runPaginatedQuery (query: { [key: string]: any }, page: number, size: number, sort: { [field: string]: 1 | -1 } = {}): Promise<IPaginatedQueryResult<{ events: IEvent<TEntity>[] }>> {
const skip = (Number(page) - 1) * Number(size)
const limit = Number(size)

Expand Down Expand Up @@ -136,6 +137,11 @@ export abstract class MongodbEventRepository<TEntity extends IEventEntity> imple
return new this._Entity().setPersistedEvents(document.events)
}

/**
* Executes the following command using a MongoDB session
* @param {ClientSession} session MongoDB client session
* @returns {{bulkUpdate: Function}} Available commands
*/
withSession (session: ClientSession) {
return {
bulkUpdate: (entities: IEventEntity[]) => this._tryRunningWithSession(() => this.bulkUpdate(entities, session), session)
Expand Down
2 changes: 1 addition & 1 deletion src/index.ts
@@ -1,7 +1,7 @@
export { EventEntity } from './classes/EventEntity'
export { IEventEntity } from './interfaces/IEventEntity'

export { IEventRepository } from './interfaces/IEventRepository'
export * from './classes/repositories/EventRepository'
export { IPaginatedQueryResult } from './interfaces/IPaginatedQueryResult'
export { MongodbEventRepository } from './classes/repositories/MongodbEventRepository'

Expand Down
4 changes: 0 additions & 4 deletions src/interfaces/IEventRepository.ts

This file was deleted.

4 changes: 2 additions & 2 deletions tsconfig.json
Expand Up @@ -42,7 +42,7 @@
/* Additional Checks */
"noUnusedLocals": true,
/* Report errors on unused locals. */
"noUnusedParameters": true,
"noUnusedParameters": false,
/* Report errors on unused parameters. */
"noImplicitReturns": true,
/* Report error when not all code paths in function return a value. */
Expand Down Expand Up @@ -73,4 +73,4 @@
"exclude": [
"./examples"
]
}
}

0 comments on commit 0f1586f

Please sign in to comment.