README.md

## About
Library for creating and managing apis and their implementations.

### Library features:

1. Calling feature asynchronously on api
2. Calling feature synchronously on implementation
3. Checking api and implmentations.
4. Defining apis and their implementations.
5. Defining default implementation.
6. Defining fallback implementation.
7. Grouping implementations.
8. Listing all apis, their features, implementations, specs and more.
9. Marking implementation features as supported, not supported and not implemented.
10. 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.2"},
        # ...
      ]
    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. :-)

## 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

Use functions in `ExApi` module to manage apis and implementations:

    {:ok, server_pid} = ExApi.register_api(MyApi)
    {:ok, ^server_pid} = ExApi.register_impl(ImplCustomModuleName)
    {:ok, ^server_pid} = ExApi.set_default_impl(ImplCustomModuleName)
    {:ok, api} = ExApi.get(MyApi)
    {:ok, impl} = ExApi.get_impl(ImplCustomModuleName)
    # and more ...

## Api Features
### Implementing feature
You can implement (or not) feature in 3 ways:

1. `:normal` - just implement it, see: `ExApi.Kernel.def_feature_impl/2`
2. `:not_supported` - mark feature as not supported (for example 3rd party api), see: `ExApi.Kernel.def_no_support/2`
3. `: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:

1. On each implementation, see: `ExApi.get_impls/1` or `YourApi.get_impls/0`
2. On default implementation, see: `ExApi.get_default_impl/1`  or `YourApi.get_default_impl/0`
3. On specified implementation, see: `ExApi.get_impl/2` or `YourApi.get_impl/1`
4. On specified implementation module, see: `YourImplementationModule` api.

### Preview features
You can see each feature status by calling `ExApi.get_feature_support/2`.

More information about each features could be find on related api pages.

## 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]},
    },
    default_groups: %{MyApi => :group_name},
    default_impls: %{MyApi => {:group_name, :impl_id}}

## Grouping features

### Default group

If some functions you can pass `{group, id}` or simpler `id`. `ExApi` will try to automatically find implementation with default group and `id`. Default group affects also implementations that does not have specified group. Grouping implementations is useful when two teams are making library/project containing implementations for same api, so here group name will be `:team_name`. Then you can easily test work of two teams simply by providing `group_name` argument in test function call.

    {:ok, impl} = ExApi.get_impl(MyApi, {:team_name, :impl_id})
    # here you can benchmark each feature for this implementation or just check if it works as expected
    {:ok, result} = impl.module.feature_name(arg1, arg2, arg3)
    # assert ....

### Defining implementation

You can set group when defining implementation:

    def_api_impl MyApi, :my_impl do
      @impl_group :group_name

      # ...
    end

### Override group on registration

You can also override default or defined group, by calling `ExApi.register_impl/2`. This is helpful in case you are implementing 3rd-party services that are deployed in multiple domains. So you have not only same api, but also same implementation for 2 domains. Using this library you can register two copies of same implementation.

    def_api_impl MyApiImpl do
      # ...
    end
    ExApi.register_impl(MyApiImpl, :local)
    ExApi.register_impl(MyApiImpl, :example)
    # ...
    ExApi.get_impls(MyApi)[:local][:my_api_impl]#.feature_name(arg1, ...)
    ExApi.get_impls(MyApi)[:example][:my_api_impl]#.feature_name(arg1, ...)
    # ...
    config :my_app, hosts: %{example: {"example.com", 80}, local: {"localhost", 4000}}

From here you should see that you can easily store informations about implementations and then dynamically register same api implementation for multiple sites specified in for example user input.

## 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 works also when you are implementing feature, so you returning `{:ok, tuple}` is optional. Every implementation result that is not a `{:error, error}` or `{:ok, result}` will be changed from `term` to `{:ok, term}`.

    def_api MyApi do
      def_feature feature_name(string)

      def_impl :a_impl do
        def_feature_impl feature_name(string) when is_bitstring(string) do
          {:error, "Oh no! Why you always pass strings?"}
        end

        def_feature_impl feature_name(term) do
          "Input: " <> inspect(term)
        end
      end
    end

    MyApi.AImpl.feature_name(123) # -> {:ok, "Input: 123"}
    MyApi.AImpl.feature_name("123") # -> {:error, "Oh no! Why you always pass strings?"}

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 your idea could be helpful. Examples and links to resources are also welcome.