View Source Fireside
Fireside is an Elixir library to install and maintain
self-contained application logic in your existing Elixir application with smart
code generation and Abstract Syntax Tree (AST) hashing.
Since the project is still in its early stages, its core functionality may
change unexpectedly. It uses Igniter under
the hood to orchestrate code generation and modifications, which means that
it's possible to hook into it and add advanced generation steps such as
adding configuration to config/config.exs
, adding a new child to the
application supervision tree, etc. Igniter itself uses
Sourceror.Zipper
,
so it is possible to do anything that Elixir's metaprogramming tools support,
but is probably unnecessary in most cases.
Installation
def deps do
[
{:fireside, "~> 0.0.1", only: :dev, runtime: false}
]
end
What is a Fireside component?
A Fireside component is a self-contained piece of application logic
with a fireside.exs
definition in its root. To create a Fireside component,
create a new Mix project with mix new my_app --sup
and define your
application logic, i.e. core functionality and tests. Then, for example, if
your Fireside component adds a Products context, you may have the following
fireside.exs
definition:
defmodule MyApp.FiresideComponent do
def config do
%{
lib: [
"lib/my_app/products.ex",
"lib/my_app/products/**/*.{ex,exs}"
],
overwritable: ["lib/my_app/products/definitions.ex"],
tests: [
"test/my_app/**/*_test.{ex,exs}"
],
test_supports: [
"test/support/my_app/products_factory.ex"
]
}
end
def setup(igniter) do
# Add custom Igniter logic here, if necessary. It will be run immediately
# after the files above are imported.
igniter
end
end
Now, it is possible to "install" this application into an existing Elixir project with Fireside. The Fireside installer will take the following steps:
- Look at
mix.exs
of the Fireside component and install all its dependencies as well as their Igniter installers. What this means is that, for example, if you are adding a Fireside component that usesAsh
andAshPostgres
, their individual Igniter installers will be run, setting up everything along the way, such as theRepo
module, requiredconfig.exs
configuration, testing utilities, etc. - Import the source code from the paths in
MyApp.FiresideComponent.config/0
. - Run
MyApp.FiresideComponent.setup/1
for optional custom code modification. - Replace
MyApp
across the entire project with the prefix of your application. - Calculate the hash of each imported file (except the files listed as
overwritable
) and add it to its top, with a note that the file should not be changed manually.
At this point, the component should become a native part of the existing Elixir
application. If its version is updated remotely, Fireside will be able to
replace all relevant parts as long as the hashes of their AST do not differ
from their originals by running fireside.update my_app
. If at any point the
component is no longer necessary, it can be removed with
mix fireside.delete my_app
. If it is no longer sufficient
and needs to be extended/customized, it should be unlocked with
mix fireside.unlock my_app
and all the files will become as if they were
never a Fireside component to begin with. If they are modified without
unlocking, Fireside will no longer be able to update them in the future,
and the generated files will contain a no-longer-useful notice that they should
not be modified.
Example application
To see an example Fireside component, check out Shopifex,
a component that provides the backbone for an e-commerce online store.
If you want to test it out, create a new Mix project with
mix new fireside_playground --sup
, clone Shopifex alongside it (in the same
parent directory), add {:fireside, "~> 0.0.1"}
to mix.exs
of
FiresidePlayground
and then run
mix fireside.install shopifex@path:../shopifex
.
Tasks
Currently supported tasks
fireside.install {app_name}@path:..
: installs an existing Fireside component under specified path.fireside.update {app_name}
: updates an installed Fireside component to reflect all changes upstream.
Planned tasks
fireside.init
: creates afireside.exs
in the root of the project. Will potentially include smart logic to guess its contents as well. This should be run only when developing a component.fireside.install {app_name}@github:..
orfireside.install {app_name}@git:..
: installs a Fireside component from a Git reference or from Github.fireside.install {app_name}@{version}
: installs a Fireside component from the Fireside directory (TBD).fireside.unlock {app_name}
: removes the lock from the provided component. Specifically, it will remove all hash information from the associated files and fromconfig/fireside.exs
. Once unlocked, a component becomes a regular part of the source code and Fireside will no longer be able to identify, track, and update it.fireside.install .. --unlocked
: same asfireside.install
but without locking the component. This can be useful if there are no plans to fetch changes from the upstream.fireside.delete {app_name}
: completely removes the installed component from the project. Note: installed dependencies will remain inmix.exs
and will need to be manually removed.
Future direction
If this project proves to be useful to people other than myself, I might create a centralized directory for Fireside components, similar to Hex, where individual developers can publish and maintain their components. This would hopefully create a helpful ecosystem of building blocks which other developers can use to rapidly iterate their app with. In principle, these components could be monetized as well.
Why does Fireside exist?
There are certain application logic components that can (and maybe should) be centralized and yet don't necessarily make sense to be a library (i.e. if they depend on a database). Typically, they become a SaaS, either self-hosted or paid for as a service, and require setting up communication channels, adding unnecessary complexity.
Fireside hopes to provide an alternative by making it as easy as possible to reuse application logic by embedding it within your Elixir monolith and still have all the benefits like version upgrades without additional engineering overhead.
Fireside's implementation in Elixir is possible thanks to @ZachDaniel's ongoing work on Igniter, which powers most of Fireside, allowing for smart, composable code generation and modification.
Why does Fireside not exist in other languages?
Elixir/Phoenix/OTP is one of the few standardized ecosystems with common conventions that allows creating an arbitrarily large distributed monolith with a solid foundation (a slide from Sasa Juric's talk comes to mind). This allows writing code that can easily be reused across teams and companies (compare this to the JavaScript or Python ecosystem with a million different options to just install a package...).
Additionally, Elixir is an immutable language that supports metaprogramming. In its closest spiritual relative and its ecosystem, Ruby and Ruby on Rails, Fireside would probably not be possible due to mutability concerns and lack of metaprogramming.