Introduce DbAdapter (#61)

Signed-off-by: Andrey Platov <andrey@hardcoreeng.com>
This commit is contained in:
Andrey Platov 2021-08-25 19:55:26 +02:00 committed by GitHub
parent 3c7495764a
commit 4f4a772630
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 118 additions and 106 deletions

View File

@ -21,6 +21,7 @@
"@anticrm/platform":"~0.6.5", "@anticrm/platform":"~0.6.5",
"@anticrm/core":"~0.6.11", "@anticrm/core":"~0.6.11",
"@anticrm/client":"~0.6.1", "@anticrm/client":"~0.6.1",
"@anticrm/dev-storage":"~0.6.6" "@anticrm/dev-storage":"~0.6.6",
"@anticrm/server-core":"~0.6.1"
} }
} }

View File

@ -13,19 +13,9 @@
// limitations under the License. // limitations under the License.
// //
import type { import type { Tx, Storage, Ref, Doc, Class, DocumentQuery, FindResult, FindOptions, TxHander, ServerStorage } from '@anticrm/core'
Tx, import { createInMemoryAdapter } from '@anticrm/dev-storage'
Storage, import { createServerStorage } from '@anticrm/server-core'
Ref,
Doc,
Class,
DocumentQuery,
FindResult,
FindOptions,
TxHander,
ServerStorage
} from '@anticrm/core'
import { createStorage } from '@anticrm/dev-storage'
class ServerStorageWrapper implements Storage { class ServerStorageWrapper implements Storage {
constructor (private readonly storage: ServerStorage, private readonly handler: TxHander) {} constructor (private readonly storage: ServerStorage, private readonly handler: TxHander) {}
@ -41,6 +31,6 @@ class ServerStorageWrapper implements Storage {
} }
export async function connect (handler: (tx: Tx) => void): Promise<Storage> { export async function connect (handler: (tx: Tx) => void): Promise<Storage> {
const serverStorage = await createStorage() const serverStorage = await createServerStorage(createInMemoryAdapter, '', '')
return new ServerStorageWrapper(serverStorage, handler) return new ServerStorageWrapper(serverStorage, handler)
} }

View File

@ -26,6 +26,7 @@
"@anticrm/platform": "~0.6.5", "@anticrm/platform": "~0.6.5",
"jwt-simple": "~0.5.6", "jwt-simple": "~0.5.6",
"@anticrm/server-chunter": "~0.6.1", "@anticrm/server-chunter": "~0.6.1",
"@anticrm/server-chunter-resources": "~0.6.0" "@anticrm/server-chunter-resources": "~0.6.0",
"@anticrm/server-core": "~0.6.1"
} }
} }

View File

@ -15,7 +15,8 @@
// //
import { start as startJsonRpc } from '@anticrm/server-ws' import { start as startJsonRpc } from '@anticrm/server-ws'
import { createStorage } from '@anticrm/dev-storage' import { createInMemoryAdapter } from '@anticrm/dev-storage'
import { createServerStorage } from '@anticrm/server-core'
import { addLocation } from '@anticrm/platform' import { addLocation } from '@anticrm/platform'
import { serverChunterId } from '@anticrm/server-chunter' import { serverChunterId } from '@anticrm/server-chunter'
@ -26,5 +27,5 @@ import { serverChunterId } from '@anticrm/server-chunter'
export async function start (port: number, host?: string): Promise<void> { export async function start (port: number, host?: string): Promise<void> {
addLocation(serverChunterId, () => import('@anticrm/server-chunter-resources')) addLocation(serverChunterId, () => import('@anticrm/server-chunter-resources'))
startJsonRpc(() => createStorage(), port, host) startJsonRpc(() => createServerStorage(createInMemoryAdapter, '', ''), port, host)
} }

View File

@ -14,4 +14,4 @@
// limitations under the License. // limitations under the License.
// //
export { createStorage } from './storage' export { createInMemoryAdapter } from './storage'

View File

@ -14,19 +14,19 @@
// //
import type { Tx, Ref, Doc, Class, DocumentQuery, FindResult, FindOptions } from '@anticrm/core' import type { Tx, Ref, Doc, Class, DocumentQuery, FindResult, FindOptions } from '@anticrm/core'
import core, { ModelDb, TxDb, Hierarchy, DOMAIN_TX, TxFactory, ServerStorage } from '@anticrm/core' import { ModelDb, TxDb, Hierarchy, DOMAIN_TX } from '@anticrm/core'
import { Triggers } from '@anticrm/server-core' import type { DbAdapter } from '@anticrm/server-core'
import * as txJson from './model.tx.json' import * as txJson from './model.tx.json'
class DevStorage implements ServerStorage { const txes = txJson as unknown as Tx[]
class InMemoryAdapter implements DbAdapter {
constructor ( constructor (
private readonly hierarchy: Hierarchy, private readonly hierarchy: Hierarchy,
private readonly triggers: Triggers,
private readonly txdb: TxDb, private readonly txdb: TxDb,
private readonly modeldb: ModelDb private readonly modeldb: ModelDb
) { ) {}
}
async findAll<T extends Doc> ( async findAll<T extends Doc> (
_class: Ref<Class<T>>, _class: Ref<Class<T>>,
@ -38,37 +38,23 @@ class DevStorage implements ServerStorage {
return await this.modeldb.findAll(_class, query, options) return await this.modeldb.findAll(_class, query, options)
} }
async tx (tx: Tx): Promise<Tx[]> { async tx (tx: Tx): Promise<void> {
if (tx.objectSpace === core.space.Model) {
this.hierarchy.tx(tx)
await this.triggers.tx(tx)
}
await Promise.all([this.modeldb.tx(tx), this.txdb.tx(tx)]) await Promise.all([this.modeldb.tx(tx), this.txdb.tx(tx)])
const derived = await this.triggers.apply(tx) }
for (const tx of derived) {
await Promise.all([this.modeldb.tx(tx), this.txdb.tx(tx)]) async init (): Promise<void> {
for (const tx of txes) {
await this.txdb.tx(tx)
await this.modeldb.tx(tx)
} }
return derived
} }
} }
/** /**
* @public * @public
*/ */
export async function createStorage (): Promise<ServerStorage> { export async function createInMemoryAdapter (hierarchy: Hierarchy, url: string, db: string): Promise<[DbAdapter, Tx[]]> {
const txes = txJson as unknown as Tx[] const txdb = new TxDb(hierarchy)
const hierarchy = new Hierarchy() const modeldb = new ModelDb(hierarchy)
const triggers = new Triggers(new TxFactory(core.account.System)) return [new InMemoryAdapter(hierarchy, txdb, modeldb), txes]
for (const tx of txes) {
hierarchy.tx(tx)
await triggers.tx(tx)
}
const transactions = new TxDb(hierarchy)
const model = new ModelDb(hierarchy)
for (const tx of txes) {
await Promise.all([transactions.tx(tx), model.tx(tx)])
}
return new DevStorage(hierarchy, triggers, transactions, model)
} }

View File

@ -14,12 +14,11 @@
// limitations under the License. // limitations under the License.
// //
import type { Doc, Tx, TxCreateDoc, TxFactory, Ref, Class } from '@anticrm/core' import type { Doc, Tx, TxCreateDoc, Ref, Class, ServerStorage, DocumentQuery, FindOptions, FindResult, Storage } from '@anticrm/core'
import core, { Hierarchy, TxFactory, ModelDb, DOMAIN_MODEL } from '@anticrm/core'
import type { Resource, Plugin } from '@anticrm/platform' import type { Resource, Plugin } from '@anticrm/platform'
import { getResource, plugin } from '@anticrm/platform' import { getResource, plugin } from '@anticrm/platform'
import core from '@anticrm/core'
/** /**
* @public * @public
*/ */
@ -60,6 +59,79 @@ export class Triggers {
} }
} }
/**
* @public
*/
export interface DbAdapter extends Storage {
init: () => Promise<void>
}
/**
* @public
*/
export type DbAdapterFactory = (hierarchy: Hierarchy, url: string, db: string) => Promise<[DbAdapter, Tx[]]>
class TServerStorage implements ServerStorage {
constructor (
private readonly dbAdapter: Storage,
private readonly hierarchy: Hierarchy,
private readonly triggers: Triggers,
private readonly modeldb: ModelDb
) {
}
async findAll<T extends Doc> (
clazz: Ref<Class<T>>,
query: DocumentQuery<T>,
options?: FindOptions<T>
): Promise<FindResult<T>> {
const domain = this.hierarchy.getDomain(clazz)
console.log('findAll', clazz, domain, query)
if (domain === DOMAIN_MODEL) return await this.modeldb.findAll(clazz, query, options)
return await this.dbAdapter.findAll(clazz, query, options)
}
async tx (tx: Tx): Promise<Tx[]> {
if (tx.objectSpace === core.space.Model) {
this.hierarchy.tx(tx)
await this.modeldb.tx(tx)
await this.triggers.tx(tx)
return [] // we do not apply triggers on model changes?
} else {
await this.dbAdapter.tx(tx)
const derived = await this.triggers.apply(tx)
for (const tx of derived) {
await this.dbAdapter.tx(tx) // triggers does not generate changes to model objects?
}
return derived
}
}
}
/**
* @public
*/
export async function createServerStorage (factory: DbAdapterFactory, url: string, db: string): Promise<ServerStorage> {
const hierarchy = new Hierarchy()
const model = new ModelDb(hierarchy)
const triggers = new Triggers(new TxFactory(core.account.System))
const [dbAdapter, txes] = await factory(hierarchy, url, db)
for (const tx of txes) {
hierarchy.tx(tx)
}
for (const tx of txes) {
await model.tx(tx)
await triggers.tx(tx)
}
await dbAdapter.init()
return new TServerStorage(dbAdapter, hierarchy, triggers, model)
}
/** /**
* @public * @public
*/ */

View File

@ -14,4 +14,4 @@
// limitations under the License. // limitations under the License.
// //
export { createStorage } from './storage' export { createMongoAdapter } from './storage'

View File

@ -13,9 +13,10 @@
// limitations under the License. // limitations under the License.
// //
import core, { Tx, Ref, Doc, Class, DocumentQuery, FindResult, FindOptions, TxCreateDoc, ServerStorage, SortingOrder, DOMAIN_MODEL, TxProcessor, ModelDb, Hierarchy, DOMAIN_TX, TxFactory } from '@anticrm/core' import type { Tx, Ref, Doc, Class, DocumentQuery, FindResult, FindOptions, TxCreateDoc } from '@anticrm/core'
import core, { TxProcessor, Hierarchy, DOMAIN_TX, SortingOrder } from '@anticrm/core'
import type { DbAdapter } from '@anticrm/server-core'
import { Triggers } from '@anticrm/server-core'
import { MongoClient, Db, Filter, Document, Sort } from 'mongodb' import { MongoClient, Db, Filter, Document, Sort } from 'mongodb'
function translateQuery<T extends Doc> (query: DocumentQuery<T>): Filter<T> { function translateQuery<T extends Doc> (query: DocumentQuery<T>): Filter<T> {
@ -26,18 +27,19 @@ function translateDoc (doc: Doc): Document {
return doc as Document return doc as Document
} }
class MongoTxProcessor extends TxProcessor { class MongoAdapter extends TxProcessor implements DbAdapter {
constructor ( constructor (
private readonly db: Db, private readonly db: Db,
private readonly hierarchy: Hierarchy, private readonly hierarchy: Hierarchy
private readonly modeldb: ModelDb
) { ) {
super() super()
} }
async init (): Promise<void> {}
override async tx (tx: Tx): Promise<void> { override async tx (tx: Tx): Promise<void> {
const p1 = this.db.collection(DOMAIN_TX).insertOne(translateDoc(tx)) const p1 = this.db.collection(DOMAIN_TX).insertOne(translateDoc(tx))
const p2 = tx.objectSpace === core.space.Model ? this.modeldb.tx(tx) : super.tx(tx) const p2 = super.tx(tx)
await Promise.all([p1, p2]) await Promise.all([p1, p2])
} }
@ -46,19 +48,6 @@ class MongoTxProcessor extends TxProcessor {
const domain = this.hierarchy.getDomain(doc._class) const domain = this.hierarchy.getDomain(doc._class)
await this.db.collection(domain).insertOne(translateDoc(doc)) await this.db.collection(domain).insertOne(translateDoc(doc))
} }
}
class MongoStorage implements ServerStorage {
private readonly txProcessor: TxProcessor
constructor (
private readonly db: Db,
private readonly hierarchy: Hierarchy,
private readonly triggers: Triggers,
private readonly modeldb: ModelDb
) {
this.txProcessor = new MongoTxProcessor(db, hierarchy, modeldb)
}
async findAll<T extends Doc> ( async findAll<T extends Doc> (
_class: Ref<Class<T>>, _class: Ref<Class<T>>,
@ -66,8 +55,6 @@ class MongoStorage implements ServerStorage {
options?: FindOptions<T> options?: FindOptions<T>
): Promise<FindResult<T>> { ): Promise<FindResult<T>> {
const domain = this.hierarchy.getDomain(_class) const domain = this.hierarchy.getDomain(_class)
console.log('findAll', _class, domain, query)
if (domain === DOMAIN_MODEL) return await this.modeldb.findAll(_class, query, options)
let cursor = this.db.collection(domain).find<T>(translateQuery(query)) let cursor = this.db.collection(domain).find<T>(translateQuery(query))
if (options !== null && options !== undefined) { if (options !== null && options !== undefined) {
if (options.sort !== undefined) { if (options.sort !== undefined) {
@ -81,42 +68,15 @@ class MongoStorage implements ServerStorage {
} }
return await cursor.toArray() return await cursor.toArray()
} }
async tx (tx: Tx): Promise<Tx[]> {
if (tx.objectSpace === core.space.Model) {
this.hierarchy.tx(tx)
await this.triggers.tx(tx)
}
await this.txProcessor.tx(tx)
const derived = await this.triggers.apply(tx)
for (const tx of derived) {
await this.txProcessor.tx(tx)
}
return derived
}
} }
/** /**
* @public * @public
*/ */
export async function createStorage (url: string, dbName: string): Promise<ServerStorage> { export async function createMongoAdapter (hierarchy: Hierarchy, url: string, dbName: string): Promise<[DbAdapter, Tx[]]> {
const client = new MongoClient(url) const client = new MongoClient(url)
await client.connect() await client.connect()
const db = client.db(dbName) const db = client.db(dbName)
const hierarchy = new Hierarchy()
const triggers = new Triggers(new TxFactory(core.account.System))
const txes = await db.collection(DOMAIN_TX).find<Tx>({ objectSpace: core.space.Model }).sort({ _id: 1 }).toArray() const txes = await db.collection(DOMAIN_TX).find<Tx>({ objectSpace: core.space.Model }).sort({ _id: 1 }).toArray()
for (const tx of txes) { return [new MongoAdapter(db, hierarchy), txes]
hierarchy.tx(tx)
await triggers.tx(tx)
}
const model = new ModelDb(hierarchy)
for (const tx of txes) {
await model.tx(tx)
}
return new MongoStorage(db, hierarchy, triggers, model)
} }

View File

@ -15,7 +15,8 @@
// //
import { start as startJsonRpc } from '@anticrm/server-ws' import { start as startJsonRpc } from '@anticrm/server-ws'
import { createStorage } from '@anticrm/mongo' import { createMongoAdapter } from '@anticrm/mongo'
import { createServerStorage } from '@anticrm/server-core'
import { addLocation } from '@anticrm/platform' import { addLocation } from '@anticrm/platform'
import { serverChunterId } from '@anticrm/server-chunter' import { serverChunterId } from '@anticrm/server-chunter'
@ -26,5 +27,5 @@ import { serverChunterId } from '@anticrm/server-chunter'
export async function start (dbUrl: string, port: number, host?: string): Promise<void> { export async function start (dbUrl: string, port: number, host?: string): Promise<void> {
addLocation(serverChunterId, () => import('@anticrm/server-chunter-resources')) addLocation(serverChunterId, () => import('@anticrm/server-chunter-resources'))
startJsonRpc((workspace: string) => createStorage(dbUrl, workspace), port, host) startJsonRpc((workspace: string) => createServerStorage(createMongoAdapter, dbUrl, workspace), port, host)
} }