View Source Glass (glass v0.1.0)
Glass is a small library which provides a way to transparently proxy function calls in a pluggable fashion.
Installation
Add :glass
to the list of dependencies in mix.exs
:
def deps do
[
{:glass, "~> 0.1.0"}
]
end
About
This is immediately useful if you're working with multiple applications potentially deployed in different releases/environments but need to call functions between them.
Usually, you could do this via the built-in :rpc
or :erpc
modules (amongst others), but
this requires ceremony and boilerplate to set up and maintain.
Glass aims to live up to its name, and the idea that the BEAM is network-transparent, by completely hiding the fact that you're not just calling a local function.
Usage
Once installed, you can use Glass
as simply as adding the following to your code:
# 1) Setup and initialize `Glass`
use Glass
# 2) Define a `GlassProxy` for the target module, which for this example, we can
# imagine is in another Elixir application which is bundled in the same umbrella
# project as the current application, but is *released separately* in a production
# environment so *we can't directly call functions in it*.
defglass ReportService, via: Glass.Methods.RPC
# 3) Be blown away by `Glass` letting you call these functions directly...
def accounts_receivable_report do
current_user()
|> MyApp.Accounting.accounts_receivable()
|> ReportService.to_excel!()
end
Configuration
Glass is designed to be pluggable and extensible, and comes with a few built-in methods for proxying function calls between modules.
Built-in Methods
Glass.Methods.Apply
(default): Useful for prototyping locally, this method simply calls the target function directly in the target module viaKernel.apply/3
.Glass.Methods.RPC
: This method uses:rpc
to call the target function in the target module. The node the rpc call is executed on is currently limited to a random node in the cluster (:erlang.nodes()
), but this may be configurable in the future.
Custom Methods
You can also define your own method for proxying function calls by implementing the
Glass.Method
behaviour. If you want to customize the way Glass
proxies function calls,
you can define your own module and pass it as the :via
option to defglass
.
defmodule MyApp.Tupleize do
@behaviour Glass.Method
@impl Glass.Method
def handle_proxy(module, function, args) do
{module, function, args}
end
end
defmodule MyApp.Users do
use Glass
defglass ReportService, via: MyApp.Tupleize
...
def users_report do
current_org()
|> list_users()
|> Enum.map(&build_report_rows/1)
|> ReportService.to_csv!()
end
end
iex(1)> MyApp.Users.users_report()
{ReportService, :to_csv!, [ ... ]}
Additional Configuration
:private
(default:true
): Whether or not to generate the proxy module as private. This is useful if you want to prevent direct access to the proxy module and force all calls to go throughGlass
. Mainly affects tab-completion in IEx.:debug
(default:false
): Whether or not to generate debug information when proxying function calls. This is useful for debugging and tracing calls between modules.
Caveats
Magic: This is a very magical library and should be used with caution. It's designed to be a transparent proxy, but this means that it can be difficult to debug when things go wrong. Use with caution and test thoroughly.
Performance: The easier it is to write network-transparent code, the easier it is to write slow network-transparent code.
Be mindful of the performance implications of calling functions between modules in this way as depending on the
:via
method used, there may be a non-trivial serialization-deserialization overhead, or limitations on how much data can be sent between nodes before issues arise.Security: This library is designed to be used in a trusted environment where you have control over the nodes in the cluster.
It's not designed to be used in a hostile environment where you need to worry about malicious actors. Be mindful of the security implications of calling functions between modules in this way.
You can mitigate some of these concerns by using the
:via
option to define your own method for proxying function calls with additional security checks, but this is left as an exercise to the reader.
Future Work
Anonymous via functions: Allow for anonymous functions to be passed as the
:via
option todefglass
to allow for more flexible proxying of function calls.Customizable debug output: Allow for custom debug output to be generated when proxying function calls.
Customizable error handling: Allow for custom error handling to be defined when proxying function calls.
Telemetry integration: Allow for
Telemetry
events to be emitted when proxying function calls to allow for better observability of function calls between modules.Testing utilities: Allow for easier testing of
Glass
-proxied functions by providing utilities to mock out theGlass
proxying mechanism.
License
Glass
is released under the MIT License. See the LICENSE file for more information.
Summary
Functions
Defines a new Glass proxy module for the given module alias.
Functions
Defines a new Glass proxy module for the given module alias.
Examples
Assuming you have a module ReportingService
in another application MyOtherApp
, which
is deployed in another release in a different node in your cluster, you can't directly
call functions in that module from your current application.
By defining a Glass proxy module, you can transparently call functions in ReportingService
,
in this case, via :rpc
.
defglass ReportingService, via: Glass.Methods.RPC
Following this, all calls to ReportingService
will be transparently proxied via :rpc
,
but these appear to the reader as completely normal, local function calls.
Additionally, you can add debug logging to your Glass proxy functions to aide in debugging:
defglass ReportingService, via: Glass.Methods.Apply, debug: true
And you can also make the proxy module public, if you need to access it directly or via the REPL.
defglass ReportingService, via: Glass.Methods.Apply, private: false
Options
:via
- The method to use for calling proxied functions. Defaults toGlass.Methods.Apply
.:private
- Whether or not to generate the proxy module as private. Defaults totrue
.:debug
- Whether or not to generate proxy debug information. Defaults tofalse
.
See Also
- The
Glass.Method
behaviour, which is the interface that must be matched for any:via
method. - The
Glass.Methods.Apply
method, which is the simplest possible method you can implement. - The
Glass.Methods.RPC
method, which is a more complex method that uses:rpc
to call functions.