ExApi
Library for creating and managing apis and their implementations.
Library features:
- Calling feature directly on implementation.
- Calling feature from api using default implementation.
- Calling feature from api using group and implementation id.
- Checking api and implmentations.
- Defining apis and their implementations.
- Defining default implementation.
- Grouping implementations.
- Listing all apis, their features, implementations, specs and more.
- Marking implementation features as supported, not supported and not implemented.
- Registering and unregistering apis and implementations.
Installation
Simply add :ex_api
to your deps in mix.exs
file and run command: mix deps.get
.
defp deps do
[
# ...
{:ex_api, "~> 1.0.0-rc.1"},
# ...
]
end
Why someone should use ExApi rather than Behaviour
or Protocol
?
We should use ExApi when we want to get list of implementations dynamically and when we do not have a target data type.
Example use case
Imagine that you are implementing unique api for similar services. First service could use JSON based api, second could use XML based api and so on …
Comparing to Enum
module you do not need to have any useful data that you need to pass and work on them. Of course you could use empty struct or struct with data that you do not want to modify and/or use, but … why?
As a developer you are going to make your code clean and short and here is solution for you. :-)
Api Features
Implementing feature
You can implement (or not) feature in 3 ways:
:normal
- just implement it, see:ExApi.Kernel.def_feature_impl/2
:not_supported
- mark feature as not supported (for example 3rd party api), see:ExApi.Kernel.def_no_support/2
:not_implemented
- just don’t implement feature and it will be automatically marked as not implemeted!
Calling feature
You can call feature in four ways:
- On each implementation, see:
ExApi.get_impls/1
orYourApi.get_impls/0
- On default implementation, see:
ExApi.get_default_impl/1
orYourApi.get_default_impl/0
- On specified implementation, see:
ExApi.get_impl/2
orYourApi.get_impl/1
- On specified implementation module, see:
YourImplementationModule
api.
Preview features
You can see each feature status on related implementation api page or simply in iex
by executing: YourImplementation.__impl_info__
. Of course you can access to your implementation module in other ways too.
More information about each features could be find related extra api pages.
Usage
Call ExApi.Api.def_api/2
macro to define api:
import ExApi.Api
def_api MyApi do
# docs, specs and features goes here ...
end
Call ExApi.Implementation.def_api_impl/5
macro to define api implementation:
import ExApi.Implementation
def_api_impl MyApi, :impl_id, ImplCustomModuleName do
# set group, implement and/or mark as not supported `MyApi` features
end
Configuration
You can specify which apis and implementations by default will be loaded into state.
use Mix.Config
config :ex_api, apis: [
{MyApi, [MyApi.FirstImpl, MyApi.SecondImpl, DifferentModuleName]},
], defaults: %{MyApi => {:group_name, :impl_id}}
Conventions
There is only one convention. ExApi
follows Elixir-way for return values.
ExApi
will help you define specs for features, for example this code:
import ExApi.Api
def_api MyApi do
@spec feature_name() :: String.t
def_feature feature_name()
end
will be readed as same as:
import ExApi.Api
def_api MyApi do
@spec feature_name() :: {:ok, String.t}
def_feature feature_name()
end
Return specs are really easily parsed. First case is moved into {:ok, result}
tuple and every other case is moved into {:error, result}
tuple.
@spec feature_name() :: String.t | String.t | String.t
# is readed as:
@spec feature_name() :: {:ok, String.t} | {:error, String.t} | {:error, String.t}
Of course you could expect more than one {:ok, result}
tuples. In this case you should change your spec like:
@spec feature_name() :: {:ok, your_first_result | your_second_result}
# or
@spec feature_name() :: {:ok, your_first_result} | {:ok, your_second_result}
This convention is important, because we expected that API is stable and never raises any exceptions, so you only need to check return value.
{:ok, my_api} = ExApi.get(MyApi)
{:ok, impl} = ExApi.get_default_impl(my_api)
case impl.module.feature_name() do
{:error, error} -> IO.inspect error # not implemented, not supported or your custom error here ...
{:ok, result} -> continue_your_work_if_feature_is_supported(result)
end
From now your API will never be limited by any of its implementations!
Contributing
Feel free to share ideas. Describe a situation when you idea could be helpful. Examples and links to resources are also welcome.