How to avoid circular dependency in lua without global variables?
Asked Answered
A

2

2

I'm using OpenResty and my app is setup as:

app/
   clients/
      photoClient.lua
   init.lua
   auth.lua

Let's say photoClient has both an unauthenticated and a authenticated endpoint (similar to an avatar endpoint that is accessible without logging in, but there may be private photos that you need to login first)

So in terms of dependencies, I have:

-- auth.lua

local photoClient = require('app.clients.photoClient')
-- this is used to show avatar on the login page

local auth = {}

auth.isAuthenticated = function ()
   -- logic to check authentication
end

return auth

and the client is

-- photoClient.lua
local auth = require('app.auth')

local photoClient = {}
photoClient.privateEndpoint = function()
   if (!auth.isAuthenticated()) {
       ngx.exit(403)
   }
   ...
end

photoClient.getAvator = function() {
   -- this is a public method used by auth
}

return photoClient

This is giving me a circular dependency issue. I've seen on other SO post that you can use global variables, i.e. to do photoClient = photoClient or require('app.clients.photoClient') but I do not want to use global variables and want to keep each module scoped to itself.

How can I do this?

Attorneyatlaw answered 27/2, 2019 at 4:27 Comment(4)
Inside client module, move the line local auth = require('app.auth') inside the body of privateEndpoint functionSenhauser
@EgorSkriptunoff is that efficient? In almost all other languages you require your modules at the beginning, global to the scope of the module itself. If I were to have anotherPrivateMethod then I'd have to add local auth = require('app.auth') there too. Lua seems different than other languages such as Node, Python in the matter.Attorneyatlaw
There is absolutely no reason to strive moving require to the top of the Lua script.Uniocular
Slightly confused, you do not want to use global variables as in the example photoClient = photoClient or require ('...') However the act of 'requiring' your module is going to create a global variable called photoClient. It is already there, so what is the harm in testing for it?Formenti
A
0

Found the solution on LuaFlare Documentation by Kate Adams: basically add package.loaded[...] = your_module before you do return your_module to every file, i.e.

-- auth.lua
local auth = {}
package.loaded[...] = auth

local photoClient = require('app.clients.photoClient')

...

return auth


-- photoClient.lua
local photoClient = {}
package.loaded[...] = photoClient

local auth = require('app.auth')

...

return photoClient

Fixed the problem. Here is the link to the book's page for anyone who is interested to read more.

Attorneyatlaw answered 27/2, 2019 at 6:21 Comment(8)
This is not a correct solution. It looks like an ugly workaround. The correct solution is to use require only where you really need this module.Uniocular
@EgorSkriptunoff can you explain why this is not a correct solution? In terms of "looks" I actually prefer this far over require the module inside every method, you call (as a preference).Attorneyatlaw
Your solution is nothing better then using globals. Actually, package.loaded is a global variable used internally by Lua. Using implementation-specific details of interpreter to solve pure-logical problem of your code is a workaround, not a solution. What you really need is a "deferred/lazy require", and there are more clean ways to do this in Lua (compared to using package.loaded). If you have many functions like provateEndpoint just write your own lazy_require() and place it at the beginning of your script.Uniocular
I see, isn't that exactly like Node require work? The require also contains a Module._load that manages the new modules and caches and is a global variable. But in Node land, when you export and import/require in another module, the bit of package.loaded that I added is self-managed by the engine directly. So I don't really see why it's the correct way in Node, and the wrong way in Lua.Attorneyatlaw
Why do you want to bring your old Node-land habits to Lua land? Lua is better. :-)Uniocular
BTW, your solution doesn't solve the problem. You must place package.loaded[...] = at the top of a module, prior to invoking any requireUniocular
Haha touche! Okay, thanks for your help. Want to write an answer so I can mark as solution?Attorneyatlaw
Why writing another answer? Instead I've edited yours.Uniocular
Q
0

Two main solutions:

Split

Split up auth into two modules that handle different aspects: an auth module for common logic that might be used by photoClient and a login module that is the login page.

This solution will prevent the same problem in other cases where you need to use auth.

Inject

In parent code (init.lua) that owns both of these modules, inject one into the other (call a function that passes the module).

Quillen answered 10/3, 2022 at 18:44 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.