I am currently developing a Hono API application practicing the basics of Clean Architecture (you can look it up), but tldr it provides ways to isolate layers between each other, making it easier to test different parts of the application due to having the layers loosely coupled together.
However, I am currently having issues with trying to attain this loose coupling due to Cloudflare Workers having the bindings (and therefore my database connection) only being available inside the context variable of Hono. I am forced to create a function in the application layer that accepts the bindings from the interface to the application before executing the application layer, and this makes the testing of this layer impractical, let alone this already violates the clean architecture principle.
Here's a snippet of my code with the issue. In this one, I am only referencing an environment variable that can be accessed in the context variable which is a string, but this could be anything.
// application/service/jwtservice.ts
export class JWTServiceImpl implements JWTService {
private secret: string = '';
bindEnv(env: EnvContextProvider): void {
this.secret = env.jwtSecret;
}
sign(payload: ReturnType<typeof Users.prototype.getSafeData>): string {
return jwt.sign(payload, this.secret);
}
verify(token: string): boolean {
return jwt.verify(token, this.secret) ? true : false;
}
}
// application/authentication/login.ts
export class LoginUseCase {
constructor(
private readonly usersRepository: UsersRepository,
private readonly jwtService: JWTService,
) {}
async verifyToken(token: string, env: EnvContextProvider): Promise<void> {
this.jwtService.bindEnv(env);
// this feels wrong
const data = this.jwtService.verify(token);
if (!data) {
throw new AuthenticationError('Invalid Token');
}
}
}
// interfaces/controller/authcontroller.ts
export class AuthController {
public router: OpenAPIHono<OpenAPIHonoSettings>;
constructor(
app: OpenAPIHono<OpenAPIHonoSettings>,
private loginUseCase: LoginUseCase,
) {
this.router = app;
this.setupRoutes();
}
private setupRoutes() {
return [
this.router.openapi(checkLoginRoute, async (c) => {
const token = getCookie(c, 'loginToken3');
if (!token) {
return c.json({ success: false }, StatusCodes.UNAUTHORIZED);
}
try {
await this.loginUseCase.verifyToken(token, new EnvContextProviderImpl(c));
// this feels wrong, im supposed to do this in the DI container/app init
return c.json({ success: true }, StatusCodes.OK);
} catch (error) {
if (error instanceof AuthenticationError) {
return c.json({ success: false }, StatusCodes.UNAUTHORIZED);
}
c.var.logger.error('Internal Server Error', { error });
return c.json({ success: false }, StatusCodes.INTERNAL_SERVER_ERROR);
}
}),
] as const;
}
}
I would like to be enlightened on how I should approach this. I looked up using context providers for this but it still involves injecting this context into the use case, which I believe am already doing.