npm i --save https://github.com/tmukammel/lib-repo-discovery.git
// In the application layer
// BaseRepoDirectInvoker.ts
export default class BaseRepoDirectInvoker implements IAppRepoInvoker {
// ...
// Likely implementation of IAppRepoInvoker interface
/************************************************************************************************/
async get<JSON, Model>(query: JSON, isCollection?: boolean): Model | Model[] {
query = await queryParser.parse(query);
return isCollection
? this.service.repository.getModelCollection(query)
: query?.params?.hasOwnProperty('id')
? this.service.repository.getModelById(query?.params?.id, 'include' in query ? query.include : null)
: this.service.repository.getModel(query);
}
async validate<JSON, Model>(query: JSON, validationLogic: (data: Model) => boolean): Promise<boolean> {
const res = await this.service.repository.getModelById(
query?.params?.id,
'include' in query ? query.include : null
);
return validationLogic(res);
}
transact<JSON, Transaction, Model>(method: string, query: JSON, transaction: Transaction, data?: Model): Model {
enum AllowedMethods {
post = 'POST',
put = 'PUT',
patch = 'PATCH',
delete = 'DELETE'
}
switch (method) {
case AllowedMethods.post:
return this.service.repository.createModel(query, transaction);
case AllowedMethods.put:
case AllowedMethods.patch:
return this.service.repository.updateModel(data, query, transaction);
case AllowedMethods.delete:
return this.service.repository.deleteModel(query, transaction);
}
}
/************************************************************************************************/
}
// In the application bootstrap file
// App.ts
// Likely registration of BaseRepoDirectInvoker as IRepositoryInvoker
repoDiscovery: RepositoryDiscovery = RepositoryDiscovery.instance()
forEach((ModelName) => {
db: BaseORM = new Sequelize()
model: IModel = new UserModel(db)
repo: IRepository = new BaseRepo(model)
service: IService = new BaseService(repo)
controller: IController = BaseController(service)
// ... add to router
// add BaseRepoDirectInvoker to RepositoryInvokerRegistry
repoInvoker: IAppRepoInvoker = new BaseRepoDirectInvoker(service)
repoInvokerAdapter: IRepositoryInvoker = new RepositoryInvokerAdapter(repoInvoker)
repoDiscovery.addToRepositoryInvokerRegistry(repoInvokerAdapter)
})
// Likely code sample of ivoker discovery and invocation of repository
// some logic module...
repoDiscovery: IAppRepoDiscovery
func constructor(repoDiscoverer: IAppRepoDiscovery) {
this.repoDiscovery = repoDiscoverer
}
repoInvoker: IAppRepoInvoker = this.repoDiscovery.getRepositoryInvoker("Users")
userData: UserModelStab = await repoInvoker.get({/*JSON query*/}, false)
- The lib should allow resource repository discovery across monolyth, packages and microservices
- should be communication protocol agnostic (e.g: should support - HTTP, google RPC, socket, etc...)
- client should use common interface for any communication protocol
lib-repo-discovery should enable a monolyth, monorepo packages, micro services to allow repository invocation across them without needing to import the specific repository. The lib-repo-discovery interfaces will specifically allow us to validate data, get data, run transactional updates on any repository across the system.
This lib should have three alternative working layers for communication
- method call within monolyth probably with multiple packages
- event driven communication between processes within same service (instance)
- over the network communication between services in microservices environment
- over the network communication can have different protocols
- HTTP
- gRPC
- socket
- over the network communication can have different protocols
Example,
Suppose JobService & UserService are two microservices.
We need to fetch UserProfile including any JobApplications.
And that means, we need UserProfile from UserService and JobApplications from JobService.
The client browser requested something like
api/user_profile/:id?include=JobApplications
.In UserProfileService we get the user data but JobService is in different microservice.
Now we use the lib-repository-discovery to get the jobApplicationsRepoInvoker and call
jobApplicationsRepoInvoker.get(query: {where: {user_id: id}}, isCollection?: true)
.
Currently this lib only work for packages running in the same node instance.
In cluster environemnt, each cluster will have a full set of service packages running in each node with it's own instance of lib-repo-discovery, and lib-repo-discovery will route discovery calls to service packages running only within the node instance. In the following versions we should also be able to serve over the network calls using different protocols.
Class diagram edit
classDiagram
direction TB
class RepositoryDiscovery {
private static RepositoryDiscovery _instance
private Map<string, IRepositoryInvoker> registry
private constructor()
public static getInstance(): RepositoryDiscovery
public getRepositoryInvoker(key: string): IRepositoryInvoker | undefined
public addToRepositoryInvokerRegistry(key: string, repoInvoker: IRepositoryInvoker): boolean
}
class IRepositoryInvoker {
<<interface>>
get<Q, M>(query: Q, isCollection?: boolean): M | M[]
validate<Q, D>(query: Q, validationLogic: (data: D) => boolean): Promise<boolean>;
transact<Q, T, D>(method: string, query: Q, transaction: T, data?: D): D;
}
class IRepositoryInvokerRegistration {
<<interface>>
addToRepositoryInvokerRegistry(key: string, repoInvocator: IRepositoryInvoker): boolean;
}
RepositoryDiscovery ..|> IRepositoryInvokerRegistration: implements
RepositoryDiscovery "1" *-- "1..n" IRepositoryInvoker : composition