Why usecase controller is extending an infrastructure class?
See original GitHub issueApp layer shouldn’t be aware of infrastructure implementation, why we extend an infrastructure implementation class?
I thought that the usecase controller shouldn’t know about if the usecase is implemented through websocket, HTTP, TCP/IP or Message Broker; this makes the BaseController coupled to the HTTP protocol (VERBS).
I think the idea would be to return always a Result<ControllerResponse>, develop a infrastructure implementation like HTTPRequestHandler (which requires a UseCase’s Controller) with HTTP verbs as methods, and then reuse it by Express, Koa… even at the same runtime or not. (progressive library migration, performance testing…)
We could create another TCPRequestController, MQRequestController, which all of them requires the same as HTTPRequestHandler.
export interface IRequestHandler<RQ> {
req: RQ;
handle<RQ>(req: RQ, controller: any): void;
handle<RQ, RS>(req: RQ, controller: any, res: RS): void;
}
export interface IHttpRequestHandler<RQ, RS> extends IRequestHandler<RQ> {
res: RS;
}
export abstract class HttpRequestHandler<RQ, RS> implements IHttpRequestHandler<RQ, RS>/*, IHttpRequestHandlerResponses*/ {
req: any;
res: any;
controller: any;
public handle<RQ, RS>(req: RQ, controller: any, res?: RS): void {
this.req = req;
this.res = res;
this.controller = controller;
this.controller.call();
};
abstract jsonResponse(): any;
}
HttpRequestHandler could have all HTTP verbs implemented or just leave the responsability for the ending library implementation:
export interface IExpressRouteHandler extends IHttpRequestHandler<express.Request, express.Response> {}
export abstract class ExpressRouteHandler extends HttpRequestHandler<express.Request, express.Response> {
req: express.Request;
res: express.Response;
public static jsonResponse(res: express.Response, code: number, message: string) {
return res.status(code).json({ message });
}
public ok(res: express.Response, dto?: string) {
if(!!dto) {
return res.status(200).json(dto);
} else {
return res.sendStatus(200);
}
}
public created<T>(res: express.Response) {
return res.sendStatus(201);
}
public clientError(message?: string) {
return ExpressRouteHandler.jsonResponse(this.res, 400, message ? message : 'Unauthorized');
}
public unauthorized(message?: string) {
return ExpressRouteHandler.jsonResponse(this.res, 401, message ? message : 'Unauthorized');
}
public paymentRequired(message?: string) {
return ExpressRouteHandler.jsonResponse(this.res, 402, message ? message : 'Payment required');
}
public forbidden(message?: string) {
return ExpressRouteHandler.jsonResponse(this.res, 403, message ? message : 'Forbidden');
}
public notFound(message?: string) {
return ExpressRouteHandler.jsonResponse(this.res, 404, message ? message : 'Not found');
}
public conflict(message?: string) {
return ExpressRouteHandler.jsonResponse(this.res, 409, message ? message : 'Conflict');
}
public tooMany(message?: string) {
return ExpressRouteHandler.jsonResponse(this.res, 429, message ? message : 'Too many requests');
}
public todo() {
return ExpressRouteHandler.jsonResponse(this.res, 400, 'TODO');
}
public fail(error: Error | string) {
console.log(error);
return this.res.status(500).json({
message: error.toString()
});
}
}
I tried to deal with this but I’ve found a concern: What kind of response should the usecase controller return?
Here’s my alpha repository https://github.com/imsergiobernal/efecto-kettlebell
Issue Analytics
- State:
- Created 4 years ago
- Comments:7 (4 by maintainers)
Top GitHub Comments
There’s an architectural principle called Package By Component.
Notice how easy it is to determine exactly what the features of this app are? That’s called package by component/module. It’s when we put everything related to a component (the use cases are our components) into a single module. When we use the name of the use case as the folder name the app is practically telling us what it can do, without needing to look deep inside the files and classes.
That phenomenon is called Screaming Architecture. The app is screaming at us, letting the reader know exactly what problems it solves and what the domain is, with the actors and use cases. The alternative to this is *Package By Infrastructure.
Package by infrastructure is when you organize code according to the type of construct it is (
controllers
,application layer
,infra
, etc). When we do that, it hurts our ability to quickly identify where features are, and features get split across folders and files.I really punched down on Uncle Bob’s approach to designing applications by centering them around the use cases. That means that every time I start a new project, I’ll identify the
actors
, theuse cases
and whichsubdomain
those actors and use cases belong to.When we package by component we get +1 readability, +1 cohesion (cohesive units of code).
Let’s look a little bit deeper at the createPost/ folder for example.
Here’s really what I mean. In this cohesive component, we have everything we need in order to support the CreatePost.ts use case.
We have a:
And let’s say for some reason we wanted to hook this use case up to a serverless function.
Depending on the service, we might be able to just export the controller and use that, but we might also need to define another adapter in order for the use case to be able to be used by the external infrastructure concern.
My point is: everything is centered around the use cases of the application.
It makes sense to co-locate things that are used together and their entire purpose of being is to work with each other (controller, dto, error namespace, use case).
This is the very essence of the Single Responsibility Principle.
I don’t think you’d want to do that. What’s being exported from the module is the express controller and the use case itself.
The use case is what has reusability because it’s an application layer concern. The express controller does not. It has a specific purpose: to be hooked up to an express RESTful API.
If you for some reason needed to support another delivery mechanism, like GraphQL or SOAP, you could dependency inject the
DownvoteComment
use case into a GraphQLController or a SoapController (each other specific infrastructure layer concerns).@stemmlerjs
Clean Architecture book, Use Case concept, page 188
I throw the following question. Can I reuse this use-case to a Console output without duplicating?