4

Context

I am currently working on a Typescript Lambda project where we are planning to refactor our code to make use of dependency injection using the Tsyringe library. We have a typical MVC structure for projects except instead of the Repo/Database layer we have a proxy layer which calls a third-party service over the rest API to fetch the required data.

Project structure

The catch is that the proxy layer will have a single interface defined and it will have multiple implementations among which one needs to be injected depending upon the business decision. For example AuthProxy is an interface which contains a login method, and it has two different implementation classes KeycloakAuthProxyImpl and AuthZeroAuthProxyImpl. These two implementations will be in 2 separate folders say AuthZero and KeyCloak and while building we pass an argument like --folderName so only one implementation will be available in runtime for dependency injection.


The problem

The problem we are facing with Tsyringe (I have evaluated some other libraries too) is that interface-based dependency injection needs explicit token-based registration with ioc-container in the main.ts page(In my case, the handler function file). So as per theory, I should be registering it as follows.

hanlder function with dependency injection.

But in our case, this is not possible. Because say we are building it as --keycloak as an argument, then AuthZearoAuthProxyimpl will be ignored while compiling and hence the code will break in line 14 at runtime.

We tried to move that dependency registration logic to the corresponding implementation class so that each implementation class will be self-contained and isolated so that there won't be any runtime issues. But then these are not even being registered for dependency injection and we get an error saying Attempted to resolve unregistered dependency token: "AuthProxy". This is expected as per the file loading of javascript.

AuthZearoImpl class

KeycloakImpl class.

keycloakImpl class.

We even tried using @registry decorator which can be found commented in the images, but it also didn't make any difference.


Even though I haven't tried any other dependency injection libraries of Typescript, from my little research, most of them follow more or less the same pattern for interface-based dependency injection and I am anticipating the same issue in those also. Is there any other workaround through which I can resolve this issue, or is it even possible with typescript?

PS: I don't have much expertise in js and typescript, the above keywords are based on my experience with spring and java. Please ignore if I have misused any js specific terminologies while explaining the issue.

Code and project structure

sjsam
  • 21,411
  • 5
  • 55
  • 102
sebin vincent
  • 330
  • 3
  • 12

1 Answers1

0

I had similar problems with tsyringe and I found a much better approach.

Here is how I would solve this with a different DI lib iti and also remove many lines of code:

  import { createContainer } from "iti"
  import express from "express"
  // import other deps

  const container = createContainer()
    .add({
      authProxy: () =>
        Math.random() > 0.5
          ? new KeycloakAuthProxyImpl()
          : new AuthZearoAuthProxyImpl(),
    })
    .add((ctx) => ({
      authService: () => new AuthServiceV3(ctx.authProxy),
    }))
    .add((ctx) => ({
      server: () => {
        const app = express()
        app.all("/login", async (req, res) => handler(req, ctx.authService))
        return app
      },
    }))

  // start your server / lambda
  const server = container.get("server")
  server.listen(3000)

I've also refactor other parts of the app and got rid of singletons and made code IMO a bit simpler to read

I’ve created an interactive playground with a mock your app: https://stackblitz.com/edit/json-server-ntssfj?file=index.ts

Here are some links to the lib:

codeexample

  • Thanks for making effort to answer my question. This approach would create only one object (Either Keycloak or Authzearo) in runtime. But even then both KeyCloakAuthProxyImpl and AuthZearoProxyImpl have to be compiled to javascript and taken to runtime in order for the ternary operator to work. I was looking if some other approaches are available to make it more elegant(by eliminating the ternary operator) and reduce build time. – sebin vincent Oct 10 '22 at 16:51
  • I've updated the example and I think it will how you want it to work now. https://stackblitz.com/edit/json-server-ntssfj?file=index.ts But I am not 100% sure I understood you correctly. But I am confident that `iti` can deliver :) I hope it solve it now. Let me know how it went. We will get there ;) – Nick Olszanski Oct 11 '22 at 17:58
  • In the example above, any babel / webpack will tree shake an unused code right away. Obviously can then code against an interface and put those factories / functions out this file for readability – Nick Olszanski Oct 11 '22 at 18:00
  • In the end it boils down to how your builder works too. – Nick Olszanski Oct 11 '22 at 19:00