CB-2344 refactor: dependency injection for execution context

This commit is contained in:
Wroud
2022-09-09 14:23:04 +03:00
parent e3a611a89c
commit 21afbe21cd
14 changed files with 176 additions and 24 deletions
@@ -10,23 +10,39 @@ import type { IAsyncContextLoader, IContextLoader, IExecutionContext, ISyncConte
export class ExecutionContext<TData> implements IExecutionContext<TData> {
readonly contexts: Map<IContextLoader<any, TData>, any>;
readonly contextCreators: Map<IContextLoader<any, TData>, IContextLoader<any, TData>>;
constructor(private data: TData, context?: IExecutionContext<any>) {
this.contexts = context?.contexts || new Map<IContextLoader<any, TData>, any>();
constructor(private readonly data: TData, context?: IExecutionContext<any>) {
this.contexts = context?.contexts || new Map();
this.contextCreators = context?.contextCreators ?? new Map();
}
hasContext(loader: IContextLoader<any>): boolean {
return this.contexts.has(loader);
addContextCreators(
creators: [IContextLoader<any, TData>, IContextLoader<any, TData>][]
): void {
for (const [key, value] of creators) {
this.contextCreators.set(key, value);
}
}
getContext<T>(token: ISyncContextLoader<T, TData>): T
getContext<T>(token: IAsyncContextLoader<T, TData>): Promise<T>
hasContext(loader: IContextLoader): boolean {
return this.contextCreators.has(loader) || this.contexts.has(loader);
}
getContext<T>(token: ISyncContextLoader<T, TData>): T;
getContext<T>(token: IAsyncContextLoader<T, TData>): Promise<T>;
getContext<T>(token: IContextLoader<T, TData>): Promise<T> | T {
if (this.contexts.has(token)) {
return this.contexts.get(token);
}
const value = token(this, this.data);
let value: T | Promise<T>;
if (this.contextCreators.has(token)) {
value = this.contextCreators.get(token)!(this, this.data);
} else {
value = token(this, this.data);
}
if (value instanceof Promise) {
return this.getAsyncContext(token, value);
+15 -2
View File
@@ -9,6 +9,7 @@
import { flat } from '@cloudbeaver/core-utils';
import { ExecutionContext } from './ExecutionContext';
import { executionExceptionContext } from './executionExceptionContext';
import { ExecutorHandlersCollection } from './ExecutorHandlersCollection';
import { ExecutorInterrupter, IExecutorInterrupter } from './ExecutorInterrupter';
import type { IExecutionContext, IExecutionContextProvider } from './IExecutionContext';
@@ -68,9 +69,16 @@ export class Executor<T = void> extends ExecutorHandlersCollection<T> implements
context: IExecutionContext<T>,
scoped: Array<IExecutorHandlersCollection<T>>
): Promise<IExecutionContextProvider<T>> {
const interrupter = context.getContext(ExecutorInterrupter.interruptContext);
scoped = [...collection.collections, ...scoped];
context.addContextCreators(collection.contextCreators as any);
for (const scope of scoped) {
context.addContextCreators(scope.contextCreators as any);
}
const interrupter = context.getContext(ExecutorInterrupter.interruptContext);
await this.executeChain(collection, data, context, 'before');
for (const scope of scoped) {
@@ -83,7 +91,11 @@ export class Executor<T = void> extends ExecutorHandlersCollection<T> implements
for (const scope of scoped) {
await this.executeHandlers(data, context, scope.handlers, interrupter);
}
} finally {
} catch (exception: any) {
const exceptionContext = context.getContext(executionExceptionContext);
exceptionContext.setException(exception);
throw exception;
} finally {
await this.executeHandlers(data, context, collection.postHandlers);
for (const scope of scoped) {
@@ -158,6 +170,7 @@ export class Executor<T = void> extends ExecutorHandlersCollection<T> implements
this.scheduler.schedule(data, async () => {
const context = new ExecutionContext(data);
context.addContextCreators(this.contextCreators as any);
try {
await handler(data, context);
@@ -8,6 +8,7 @@
import { makeObservable, observable } from 'mobx';
import type { IContextLoader, ISyncContextLoader, IAsyncContextLoader } from './IExecutionContext';
import type { IExecutorHandler } from './IExecutorHandler';
import type { ExecutorDataFilter, ExecutorDataMap, IChainLink, IExecutorHandlersCollection } from './IExecutorHandlersCollection';
@@ -16,12 +17,14 @@ implements IExecutorHandlersCollection<T, TResult> {
handlers: Array<IExecutorHandler<T, TResult>> = [];
postHandlers: Array<IExecutorHandler<T, TResult>> = [];
chain: Array<IChainLink<T, TResult>> = [];
readonly contextCreators: Map<IContextLoader<any, T>, IContextLoader<any, T>>;
readonly collections: Array<IExecutorHandlersCollection<T, TResult>>;
protected initialDataGetter: (() => T) | null;
private readonly links: Map<IExecutorHandlersCollection<any, TResult>, IExecutorHandlersCollection<T, TResult>>;
constructor() {
this.links = new Map();
this.contextCreators = new Map();
this.collections = [];
this.initialDataGetter = null;
@@ -34,6 +37,22 @@ implements IExecutorHandlersCollection<T, TResult> {
});
}
addContextCreator<TContext>(
context: ISyncContextLoader<TContext, T>,
creator: ISyncContextLoader<TContext, T>
): this;
addContextCreator<TContext>(
context: IAsyncContextLoader<TContext, T>,
creator: IAsyncContextLoader<TContext, T>
): this;
addContextCreator<TContext>(
context: IContextLoader<TContext, T>,
creator: IContextLoader<TContext, T>
): this {
this.contextCreators.set(context, creator);
return this;
}
setInitialDataGetter(getter: (() => T) | null): this {
this.initialDataGetter = getter;
return this;
@@ -17,7 +17,13 @@ export interface IExecutionContextProvider<TData> {
}
export interface IExecutionContext<TData> extends IExecutionContextProvider<TData> {
readonly parent?: IExecutionContext<any>;
readonly contexts: Map<IContextLoader<any, TData>, any>;
readonly contextCreators: Map<IContextLoader<any, TData>, IContextLoader<any, TData>>;
addContextCreators(
creators: [IContextLoader<any, TData>, IContextLoader<any, TData>][]
): void;
}
export type IAsyncContextLoader<T = any, TData = any> = (
@@ -6,7 +6,7 @@
* you may not use this file except in compliance with the License.
*/
import type { IExecutionContextProvider } from './IExecutionContext';
import type { IAsyncContextLoader, IContextLoader, IExecutionContextProvider, ISyncContextLoader } from './IExecutionContext';
import type { IExecutorHandler } from './IExecutorHandler';
export type ExecutorDataFilter<T> = (data: T, contexts: IExecutionContextProvider<T>) => boolean;
@@ -25,9 +25,19 @@ export interface IExecutorHandlersCollection<T = unknown, TResult = any | Promis
readonly postHandlers: Array<IExecutorHandler<T, TResult>>;
readonly chain: Array<IChainLink<T, TResult>>;
readonly collections: Array<IExecutorHandlersCollection<T, TResult>>;
readonly contextCreators: Map<IContextLoader<any, T>, IContextLoader<any, T>>;
setInitialDataGetter(getter: () => T): this;
addContextCreator<TContext>(
context: ISyncContextLoader<TContext, T>,
creator: ISyncContextLoader<TContext, T>
): this;
addContextCreator<TContext>(
context: IAsyncContextLoader<TContext, T>,
creator: IAsyncContextLoader<TContext, T>
): this;
before: <TNext>(executor: IExecutorHandlersCollection<TNext, TResult>, map?: ExecutorDataMap<T, TNext>) => this;
next: <TNext>(executor: IExecutorHandlersCollection<TNext, TResult>, map?: ExecutorDataMap<T, TNext>) => this;
addCollection: (collection: IExecutorHandlersCollection<T, TResult>) => this;
@@ -9,6 +9,7 @@
import { flat } from '@cloudbeaver/core-utils';
import { ExecutionContext } from './ExecutionContext';
import { executionExceptionContext } from './executionExceptionContext';
import { ExecutorHandlersCollection } from './ExecutorHandlersCollection';
import { ExecutorInterrupter, IExecutorInterrupter } from './ExecutorInterrupter';
import type { IExecutionContext, IExecutionContextProvider } from './IExecutionContext';
@@ -55,9 +56,16 @@ export class SyncExecutor<T = void> extends ExecutorHandlersCollection<T> implem
context: IExecutionContext<T>,
scoped: Array<IExecutorHandlersCollection<T>>
): IExecutionContextProvider<T> {
const interrupter = context.getContext(ExecutorInterrupter.interruptContext);
scoped = [...collection.collections, ...scoped];
context.addContextCreators(collection.contextCreators as any);
for (const scope of scoped) {
context.addContextCreators(scope.contextCreators as any);
}
const interrupter = context.getContext(ExecutorInterrupter.interruptContext);
this.executeChain(collection, data, context, 'before');
for (const scope of scoped) {
@@ -70,6 +78,10 @@ export class SyncExecutor<T = void> extends ExecutorHandlersCollection<T> implem
for (const scope of scoped) {
this.executeHandlers(data, context, scope.handlers, interrupter);
}
} catch (exception: any) {
const exceptionContext = context.getContext(executionExceptionContext);
exceptionContext.setException(exception);
throw exception;
} finally {
this.executeHandlers(data, context, collection.postHandlers);
@@ -144,6 +156,8 @@ export class SyncExecutor<T = void> extends ExecutorHandlersCollection<T> implem
const data = this.initialDataGetter();
const context = new ExecutionContext(data);
context.addContextCreators(this.contextCreators as any);
try {
handler(data, context);
} finally {
@@ -0,0 +1,16 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2022 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
export function executionExceptionContext() {
return {
exception: null as null | Error,
setException(exception: Error) {
this.exception = exception;
},
};
}
@@ -3,6 +3,7 @@ export * from './TaskScheduler/ITask';
export * from './TaskScheduler/Task';
export * from './TaskScheduler/TaskScheduler';
export * from './ExecutionContext';
export * from './executionExceptionContext';
export * from './Executor';
export * from './ExecutorHandlersCollection';
export * from './ExecutorInterrupter';