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

yawaramin
In the Dialyzer docs ( http://erlang.org/doc/man/dialyzer.html#requesting-or-suppressing-warnings-in-source-files ), there is a way to tu...
New
itssasanka
Hi all, Trying to get some more clarity over utc_datetime and naive_datetime for Ecto: https://hexdocs.pm/ecto/Ecto.Schema.html#module-...
New
aalberti333
As the title describes, I’m trying to run Enum.map() over a list of key/value pairs, where the value is a map. My data looks like this: ...
New
Harrisonl
We have an ECS cluster with 4 services, where each task joins a single cluster, via discovery ECS discovery service. Currently when I de...
New
SoCreat
i’m a new one to elixir which editor can i use vs code? or atom? Thanks! :smiley:
New
mgjohns61585
Could someone help me? I'm making my first elixir program, number guessing game. I can't figure out how to convert the user's guess from ...
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
fayddelight
I tried installing elixir 1.11.2 erlang 23.3.4 via asdf in my zsh shell. Enabled the versions locally and globally. When I list them ...
New
LegitStack
I’m trying to make a websocket server in Phoenix or raw Elixir. I heard about gun, I think I could use cowboy, but since I’m not that sma...
New
vrod
I am using the Starship cross-shell prompt – it seems pretty nice, but I get some errors: [WARN] - (starship::utils): Executing command ...
New

Other popular topics Top

pmjoe
I have a relationship of love and hate with Elixir. Lots of things are just absolutely right, but there are some things that are kind of ...
New
openscript
Hello! Sorry for this astonishing simple question, but I’m really stuck. I try to set up the intellij-elixir plugin, but I don’t know ho...
New
ycv005
I have followed this StackOverflow post to install the specific version of Erlang. And When I am running mix ecto.setup then getting fol...
New
SoCreat
i’m a new one to elixir which editor can i use vs code? or atom? Thanks! :smiley:
New
fireproofsocks
I’m working on defining a simple Ecto schema for a table (in PostGres), but I don’t see where I can define a column as NOT NULL. Conside...
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
script
If I have a string “1000 cfu/ml” . I want to remove the characters and / and space . So the string is like this "1000" What is the ...
New
stefanchrobot
What’s the safe way to decode a JSON string into a struct? I want to avoid calling String.to_atom. Jason.decode can give me a map with st...
New
rms.mrcs
Hi, I need to transform a list of numbers into a map where the keys are the indexes and the values are the original values of the list....
New
shahryarjb
Hello, I have map which I want to convert it to string like this: the map: %{last_name: "tavakkoli", name: "shahryar"} the string I ne...
New

We're in Beta

About us Mission Statement