arjun289

arjun289

Architecture to allow creation of third party plugins for an E-Commerce App

I am working on this particular E-Commerce application, it’s a fully open source project.

It’s an umbrella with three apps:

  • A core app to handle logic and db related things.
  • An api app to expose APIs for customer frontend client.
  • A phoenix app for the admin interface.

The next phase of our development is to design the system in a manner that it’s very simple for people to create plugins or extensions. Also, the design to integrate the plugins should be such that the admin has the flexibility to add or remove them from the admin interface.

It is similar to how Shopify allows installation of extensions. We don’t fully understand how extension installation works for individual sellers.

e.g.
Let’s say the admin wants to install a library to provide support for marketing in the system he/she should be able to install the plugin created for the purpose.
What this plugin would be capable of?

  • Track User login and send a welcome email.
  • Check for abandoned carts etc.

The plugin should be created in a manner that once it is installed it should be able to track the call to session controller'slogin` action and send a ping to the third party service. This can be done by somehow injecting code which comes from the extension. e.g. making a specific api call to third-party service.

Constraints:

  • Can’t include the library in mix.exs as the number of packages can be many.
  • Since this is a multitenant app these installations are also seller specific, every seller can have a unique combination of plugins.
  • We can not have a central

One of the approaches is to inject a piece of code in the controller action to be tracked, to send an event to another library which based on metadata that the event sent(which includes a list of plugins for that particular seller) makes a decision and makes calls to the respective third party services.

What could be a possible design solution to provide the functionality?

Most Liked

edwildgoose

edwildgoose

I think this requirement is quite common and desirable.

For example lets imagine I wanted to build an email server to replace my current usage of postfix/sendmail/etc. Or I want an XMPP server (to pick an example where we can observe a plugin architecture on a high quality app)

In all these cases it’s desirable to ship the app compiled and then have the customer be able to configure it as dynamically as possible for their use case, possibly including optional functionality that they may write/create themselves

Part 1)
The lack of mix requirement seems workable because one can load any beam files you find and I guess it would be possible to dynamically start included applications if they conform to some interface. This would allow our main app to startup dependent apps which offer some kind of very dynamic plugin functionality

An alternative would be to relax the contraint on recompiling the main app and include code in the main app to start your plugins and include their code

Part 2)
You need some kind of hook architecture to allow your main app to call your plugins. This seems to be where you get furthest away from the Elixir way to do things. (Meaning that Elixir encourages everything to be explicit and it discourages clever tricks involving side effects, eg message bus and event handler solutions)

Simplest is explicitly passing hook functions to key modules. eg a module offering http client functionality could pass a callback module which implements the relevant hook code. Disadvantage of this technique is that it doesn’t scale to running multiple modules of hooks and must be defined quite explicitly in the calling code (would be more difficult to make dynamic)

Probably next up would be a plug style architecture, so that you very explicitly include plugin functionality into obvious hook functions, however, it’s much easier to specify callback chains and create a clear way for callbacks to veto further callbacks getting executed.

Next up would be that the hooks are maintained in a run time variable, so for example think gen_event or something you build yourself from genservers, genstage, etc. Dynamically things can start to listen to your hooks or remove themselves. This now allows external code to decide to compose or decompose itself from your main app. eg some config file could decide to attach some functionality to a hook. Also now you have the ability to run up multiple instances of a module and hook it to multiple hooks, eg in our mail server we might write some plugin functionality to decide if we accept/reject some email address as valid, that same module can be hooked into the hook to check both receiver address and the sender address, etc.

Finally I guess you can totally distribute the app and even remove the requirement to start all the code from the same startpoint using some kind of event bus, eg elixir registry, gproc, elixir pub/sub, kafka, etc. This has some challenges in not starting work before all plugins are attached and configured correctly

I think probably plug and mongoose/ejabberd are some good examples of how one can create a plugin architecture.

svarlet

svarlet

It sounds like you are trying to implement something generic and flexible for all sort of usecases.

Why don’t you start by creating one such plugin? Then a second? Then a third?

This will give you insight such as:

  • is our current architecture making it hard to create plugins?
  • what’s the commonalities between the first and second plugins? with the third?

You’ll certainly get somewhere, it may not be perfect but you can always rework some bits as you go. Trying to make something lile this right in one go will either lead to analysis paralysis or something unmaintainable.

notriddle

notriddle

The big alternative to using a multi-server system (which is what @dimitarvp’s idea of using an event bus would mean) is to use luerl to allow “scripting” your application the same way JavaScript allows you to script the browser and VBA allows scripting the Microsoft Office apps.

dimitarvp

dimitarvp

This is indeed a very generic question, but you might want to look at an event bus technology or something like RabbitMQ / ZeroMQ – basically your framework can emit (publish) events to a bus / topic and the plugins can subscribe to them and act on the data. This will give you something much easier to maintain than trying to reinvent plugin architectures.

danschultzer

danschultzer

Pow Core Team

@svarlet’s answer is the way I went with extensions for Pow. I created a few extensions that I felt covered a wide surface to help make the API/structure for third party extensions.

I think you’ll have to rethink the constraint of not adding the plugins to mix.exs. The app has to be compiled, and I don’t see how you can reasonably include third party plugins without requiring them in mix.

Where Next?

Popular in Questions Top

vrod
I am using the Starship cross-shell prompt – it seems pretty nice, but I get some errors: [WARN] - (starship::utils): Executing command ...
New
clayschick
I'm trying to create a simple query to select distinct values from a column. If I only use select and distinct I get back a list of uniqu...
New
Jim
As a follow up to my earlier question: I have the code compiling and running but not getting a successful login from the rest server. ...
New
aadeshere1
I have a another noob question about loop. Since elixir is immutable, while loop is not directly possible. total = 10 while total != 0 ...
New
vonH
When I run the Plug and I recompile I wind up having to use Ctrl C to quit iex and start again. Witht the help of rlwrap I can use the cu...
New
fireproofsocks
Forgive me if this is obvious, but how does one delete a database record WITHOUT selecting it first? https://hexdocs.pm/ecto/Ecto.Repo.h...
New
electic
Hi, I am new to Elixir. I am trying to use the DateTime component to insert a date into MySQL however the there seems to be no way to fo...
New
Qqwy
Original source of discussion: This topic on the Pragmatic Programmers' Functional Web Development with Elixir, OTP, and Phoenix forum. ...
New
idi527
I’ve been re-reading swift book again and noticed that multiline strings there don’t have a trailing line break, unlike in elixir iex(2)...
New
lastday4you
I wanted to check elixir version in phoenix because i found that my elixir is 1.5 but when i use Enum.chunk_by it said the function is un...
New

Other popular topics Top

belgoros
I’m not a pro in using Regex and can’t figure out why the following behaviour happens, especially if we take into account the difference ...
New
josevalim
Hi everyone, One of the features added to Elixir early on to help integration with Erlang code was the idea of overridable function defi...
New
Tee
can someone please explain to me how Enum.reduce works with maps
New
bsollish-terakeet
Credo is smart enough to check for (something like) this: assert length(the_list) == 0 with this response: Checking if an enum is empt...
New
joaquinalcerro
Hi there, I am working with Ecto-Postgresql and I need to call all of the records from a specific table but the table has 40,000 record...
New
malloryerik
Hi, this is for people who, like me, have had some friction using .html.heex templates in VSCode. The solution seems to be, in a hyphena...
New
nsuchy
Hi. I’ve noticed that Windows Powershell has it’s own IEX command and you cannot access Elixir’s IEX due to the conflict. This isn’t a cr...
New
chensan
I have a User schema with a :from_id field set to type :string: defmodule TweetBot.Repo.Migrations.CreateUsers do use Ecto.Migration ...
New
WestKeys
Currently suffering from paralysis by [HTTP client] analysis. This is rather unusual in Elixirland as there tends to be consensus on the ...
New
AstonJ
by Lance Halvorsen Elixir and Phoenix are generating tremendous excitement as an unbeatable platform for building modern web application...
460 27162 124
New

We're in Beta

About us Mission Statement