Experimenting with Elixir Metaprogramming: Agentize
In this post, we will explore a simple experiment on Elixir metaprogramming. The experiment is called Agentize and its goal is to create a Storage module that is an Agent with a map for a given struct.
I was originally reaching for ETS, but this method turned out much simpler.
The Agentize Experiment
The idea behind Agentize is to provide an easy way to store and retrieve data for a given struct. It uses Elixir metaprogramming to generate a Storage module that is an Agent with a map for the specified struct.
The experiment is composed of two files: agentize.ex and user.ex.
agentize.ex
The agentize.ex file contains the implementation of the Agentize module.
defmodule Agentize do
defmacro agentize(struct_name) do
quote do
defmodule Storage do
use Agent
def start_link(_opts \\ []) do
Agent.start_link(fn -> %{} end, name: unquote(struct_name))
end
def put(key, value) when is_map(value) and is_struct(value, unquote(struct_name)) do
Agent.update(unquote(struct_name), fn state -> Map.put(state, key, value) end)
end
def get(key) do
Agent.get(unquote(struct_name), fn state -> Map.get(state, key) end)
end
def delete(key) do
Agent.update(unquote(struct_name), fn state -> Map.delete(state, key) end)
end
def list_users do
Agent.get(unquote(struct_name), fn state -> Map.values(state) end)
end
end
end
end
end
The agentize macro takes the name of the struct as its argument and generates a module called Storage that extends the Agent behaviour. This module defines the following functions:
- start_link: starts the Agent with an empty map as its initial state and the name of the struct as its name.
- put: updates the state of the Agent with a new value for a given key. The value must be a map that matches the specified struct.
- get: retrieves the value of a given key from the state of the Agent. delete: removes the value of a given key from the state of the Agent.
- list_users: retrieves all the values in the state of the Agent.
user.ex
The user.ex file defines a struct called User and uses the Agentize module to generate the Storage module for it.
defmodule User do
require Agentize
defstruct [:name, :email, :age]
Agentize.agentize(User)
end
The User struct defines three fields: name, email, and age. The require Agentize statement loads the Agentize module, and the Agentize.agentize(User) statement generates the Storage module for the User struct.
Example
Using the Agentize experiment can save you a lot of time and effort when experimenting with Domain-Driven Design ideas in Elixir. With just a few lines of code, you can easily persist data for your struct in an Agent with a map, without having to set up a complex database system.
# start the User Agent
{:ok, user_agent} = User.Storage.start_link()
# add a new user
new_user = %User{name: "John", email: "john@example.com", age: 30}
User.Storage.put(:john, new_user)
# retrieve the user
user = User.Storage.get(:john)
IO.puts("User name: #{user.name}")
IO.puts("User email: #{user.email}")
IO.puts("User age: #{user.age}")
# delete the user
User.Storage.delete(:john)
Conclusion
In this post, we explored a simple experiment on Elixir metaprogramming called Agentize. We learned how to generate an Agent with a map for a given struct using Elixir macros. This experiment is a good example of how metaprogramming can be used to simplify common tasks in Elixir.
Reach the author at ro dot stevaux at gmail dot com