npm i --save https://github.com/tmukammel/lib-logic-registry.git
// core (business rules) layer
// A placeholder or DTO in the core layer
// representing a data object in app layer
export type XYZRes = {
name: string;
id: number;
};
interface IRepo<R> {
get(id: number): R | undefined;
post(model: R): R | undefined;
del(id: number): R | undefined;
}
// the app layer
// the example does not clarify how dependency inversion
// is maintained between core and app layers
// hint: may be we can adopt both app layer and core layer interfaces
// in the same infra adapter for repo-orm communication
export class XYZRepo implements IRepo<XYZRes> {
store: Map<number, XYZRes> = new Map();
get(id: number): XYZRes | undefined {
return this.store.get(id) || undefined;
}
post(model: XYZRes): XYZRes | undefined {
this.store.set(model.id, model);
return this.store.get(model.id);
}
del(id: number): XYZRes | undefined {
const res = this.store.get(id) || undefined;
if (res) this.store.delete(id);
return res;
}
}
// core (business rules) layer
// some-logic.ts
import { Registerer } from 'lib-logic-registry';
export const postLogic: Logic<JSON, XYZRepo, XYZRes | undefined> = async (
query: JSON,
repo: XYZRepo
): Promise<XYZRes | undefined | Error> => {
// let's say we are not registering names with 'Mr' initial
if (query['name'].startsWith('Mr')) {
return new Error(`Not registering Males`);
}
const res: XYZRes = { name: query['name'], id: query['id'] };
return repo.post(res);
};
// register the logic
Registerer.instance.register('XYZRes', Method.post, postLogic);
import { Invoker } from 'lib-logic-registry';
// XYZService.ts
public async post(req: any) {
repo = new XYZRepo();
// try to invoke any existing business logic that may be registered
let res = await Invoker.instance.invoke('<resource name>', Method.post, query, repo);
if (!res) res = await repo.post(DTO('XYZ', query));
return res;
}
The reason to create business logic registry is to fascilitate the complete segregation of our application policy (not business rules) code from the business rules, so that the application policy code are decoupled in such a way that we can move them and their related resources and APIs to any other microservice while the business demands shift the microservice bounderies.
Class diagram edit
classDiagram
direction TB
class Logic~Q, T, R~ {
<<type>>
Logic(query: Q, repo: T) => Promise~R | Error~;
}
class LogicMap {
<<type>>
Map~string, Map~string, Logic~~
}
class Color{
<<enumeration>>
get = 'GET',
gets = 'GETs',
post = 'POST',
put = 'PUT',
patch = 'PATCH',
del = 'DELETE'
}
class ILogicReadWrite {
<<interface>>
registerLogic~Q, T R~(logicData: LogicPack~Q, T R~): void;
unregisterLogic(identifier: LogicIdentifier): void;
getLogic~Q, T R~(logicId: LogicIdentifier): Logic~Q, T R~ | undefined;
}
class LogicPack~Q, T, R~ {
<<interface>>
model: string;
method: string;
logic: Logic~Q, T R~;
}
class LogicIdentifier {
<<interface>>
model: string;
method: Method;
}
class LogicStore {
private static _instance: LogicStore;
private logics: LogicMap;
private constructor();
public static get instance(): LogicStore;
}
class IInvoke {
<<interface>>
invoke~Q, TR~(forResource: string, onMethod: Method, query: Q, repo: T): Promise~boolean | R | Error~;
}
class Invoker {
private static _instance: Invoker;
private _logicStore: LogicStore;
private constructor();
public static get instance(): Invoker;
}
class IRegister {
<<interface>>
register~Q, T R~(forResource: string, onMethod: Method, logic: Logic~Q, T R~): boolean;
unregister(forResource: string, onMethod: Method): boolean;
}
class Registerer {
private static _instance: Registerer;
private _logicStore: LogicStore;
private constructor();
public static get instance(): Registerer;
}
LogicMap "1" *-- "*" Logic : composition
ILogicReadWrite <|.. LogicStore : implements
LogicStore "1" *-- LogicMap : composition
IRegister <|.. Registerer : implements
Registerer "1" *-- LogicStore : composition
IInvoke <|.. Invoker : implements
Invoker "1" *-- LogicStore : composition