Message Bus Architecture
We can have Masters, we can have Workers, we can have Factories, we need the message bus.
A message bus is the communication provider between your modules. I'll try to realize some models like Publish/Subscribe.
None of this is boilerplated! This is only to give you an idea of what's easy with Allure.
Event Driven
If we're realizing a message bus for this, then we need a simple module through which I can communicate a task and some arguments.
📜Main.server.luau
├─📦MessageBus.luau
└─📂Modules
├─📦Calculator.luau
└─📦Multiplicator.luaulocal Allure = require(path.to.Allure)
local module = Allure:Node() {} {
Name = "Message Bus"
}
local Tree = nil :: any
-- We need initialization with the tree
-- because we can't cause a dependendy cycle
function module:OnInit(tree: Allure.NodeTree)
Tree = tree
end
function module:Event(serviceName: string, taskName: string, ...)
assert(Tree, "Message Bus not initialized!")
return Tree
:NodeFromPredicate(function(self, node) --Get node by name
return node.Name == serviceName
or node.Tags.Instance.Name == serviceName
end)
[taskName](...) --Call the task
end
return module()Then we have a simple boostrapper:
local Allure = require(path.to.Allure)
local tree = Allure:NodeTree()
:LoadDescendants(script)
:ForEach(function(self, node)
node:OnInit(self, self:NodeFromInstance(script.MessageBus))
end)And it's easy to use by the descendant modules:
Our modules though will need initialization now with given tree and messagebus
local Allure = require(path.to.Allure)
return Allure:Node() {
OnInit = function(self, tree, messagebus)
self.Tree = tree
self.MessageBus = messagebus
end,
Calculate = function(self, a, b)
return a + b
end,
Multiply = function(self, a, b)
return self.MessageBus:Event("Multiplicator", "Multiply", a, b)
end
} {} ()local Allure = require(path.to.Allure)
return Allure:Node() {
OnInit = function(self, tree, messagebus)
self.Tree = tree
self.MessageBus = messagebus
end,
Calculate = function(self, a, b)
return self.MessageBus:Event("Calculator", "Calculate", a, b)
end,
Multiply = function(self, a, b)
return a * b
end
} {} ()This solves dependency cycles and 2 modules can use each other. Isn't this neat?
Consumer - Producer (Publish/Subscribe)
Consumers and Producers can be 2 different types of Nodes.
Our message bus can be a wrapper of Allure's Node.
Or we can make it even simpler by making the standard :Subscribe and :Publish.
📜Main.server.luau
├─📦MessageBus.luau
└─📂Modules
├─📦VehicleService.luau
└─📦RaceService.luaulocal Allure = require(path.to.Allure)
return Allure:Node() {
Tree = nil :: any,
Subscribers = {}, --[string]: function
OnInit = function(self, tree)
self.Tree = tree
end,
OnStart = function(...)
--do something!
end,
Subscribe = function(self, topic, func)
-- Create the topic if it doesn't exist
if not self.Subscribers[topic] then self.Subscribers[topic] = {} end
-- Add function to the list of subscribers (hooks)
table.insert(self.Subscribers[topic], func)
-- Return an unsubscribe function
return function()
table.remove(self.Subscribers[topic], func)
end
end,
Publish = function(self, topic, ...)
-- Call all subscribers of this topic
for _, subscriber in self.Subscribers[topic] or {} do
subscriber(...)
end
end
} {} ()The Bootstrapper remains the same, but now we have an OnStart phase:
local Allure = require(path.to.Allure)
local tree = Allure:NodeTree()
:LoadDescendants(script)
:ForEach(function(self, node)
node:OnInit(self, self:NodeFromInstance(script.MessageBus))
end)
:ForEachParallel(function(self, node)
node:OnStart()
end)local Allure = require(path.to.Allure)
return Allure:Node() {
OnInit = function(self, tree, messagebus)
self.Tree = tree
self.MessageBus = messagebus
end,
OnStart = function(self)
local desubscribe = self.MessageBus:Subscribe("spawnVehicle", function(name, pos)
self:SpawnVehicle(name, pos)
--...
end)
end,
SpawnVehicle = function(self, name, position)
--...
end
} {} ()local Allure = require(path.to.Allure)
return Allure:Node() {
OnInit = function(self, tree, messagebus)
self.Tree = tree
self.MessageBus = messagebus
end,
OnStart = function(self)
local desubscribe = self.MessageBus:Subscribe("BeginRace", function()
self:BeginRace()
--...
end)
end,
BeginRace = function(self)
self.MessageBus:Publish(
"VehicleSpawn", "SomeVehicleName",
Vector3.new(10, 10, 10))
--...
end
} {} ()Enjoy Allure!
Thank you for paying attention and taking your time to read these docs or even trying out Allure, despite a very early version and many features lacking.
Allure is one of my first huge published open source projects and the Allure Ecosystem has a long way to go!