Introduction
So, One of the biggest issues when designing a library/framework Is when you need to make a call to an external dependency.
Specifically in the context of this post, when that dependency is no longer usable for any reason, what do you do? Most would then need to implement a new interface for a new dependency, and then as a result potentially make breaking changes to your API to accommodate which isn't good.
I bring this up because I see all to often libraries that directly call the external dependency in there API methods (I'm guilty of this too -_-), then have to completly change there API when the dependency changes that requires data to be structured differently.
This then breaks things for many people.
This is why I advocate separating the API and the external calls into separate classes, it does add complexity and increases the amount of code but the advantage is that when you're external dependency changes, your API doesn't, and nor does it for anyone using you're libraries.
When I think to do this (ahem) I like to separate the logic into:
- API: The API of your library 
- Provider: The logic that calls your an external dependency 
- Formatter: If needed, the logic that handles data formatting between the API and the Provider 
Basic Provider Logic
Now you can do this any way you like, so long as there is a clear separation of concerns. Using ruby as an example, I like to take advantage of the fact you can define def self.[]() to make my provider API easy to use so I can do somthing like this:
Module Providers
    class << self
        attr_writer :listing
        def [](provider)
            @listing[provider]       
        end
    end
    class Provider_Base
        def self.inherited(child, name = child.class.to_s.downcase.to_sym)
            # Normally I'd add logic to ensure that only this method can add to
            # `Providers.listing`, but I can't be arsed for this example
            Providers.listing[name] = child
        end
    end
end
The reason I do name = child.class.to_s.downcase.to_sym in the function declaration is so that if you want you can easily change the way the name is set by doing:
Module Providers
    class OtherProviderBase < Provider_Base
        def self.inherited(child)
            super(child, :some_other_naming_mechaism)
        end
    end
end
Now you can inherit from OtherProviderBase instead.
Using Your provider
Using the providers in you're api becomes super easy now:
class API
    PROVIDER_NAME = :provider_name
    def some_method
        Providers[PROVIDER_NAME].some_method
    end
end
Why do it like this? Lets say you build a new dependency provider, you can just change the provider to this:
class API
    PROVIDER_NAME = :other_provider_name
    def some_method
        Providers[PROVIDER_NAME].some_method
    end
end
End result is that the API doesn't change and won't break for you of your users.
And using the above providers example, you can add formatters like this:
class API
    PROVIDER_NAME = :provider_name
    def some_method
        Formatters[PROVIDER_NAME].some_method(
            Providers[PROVIDER_NAME].some_method)
    end
end
You can even go further and do this:
class API
    PROVIDER_NAME = :provider_name
    def some_method(*args)
        method = :some_method
        output = Providers[PROVIDER_NAME].send(method, (Formatters::Input[PROVIDER_NAME].send(method,*args))
        Formatters::Output[PROVIDER_NAME].send(method, output)
    end
end
Provider Piplines
You could even provide a mechanism (where appropriate of course) to automatically pipeline, like this maybe:
module Pipelines
    attr_reader :listing
    def pipline_for(action, *pipes)
        @listing[action] = pipes
        define_method(action, do) |*args|
            run_pipline_for(action, *args)
        end
    end
    def self.extended(child)
        child.include Instance_Methods
    end
    module Instance_Methods
       def run_pipline_for(action, *args)
           Pipelines.listing[action].inject(args) do |result, (item, custom_action)|
              item[PROVIDER_NAME].send(custom_action || action, *result)
           end
       end
    end
end
class API
    extend Pipeline
    PROVIDER_NAME = :provider_name
    pipline_for :action, Formatters::Input, [Providers, :get_remote_data], Formatters::Output
    pipline_for :other_action, Formatters::Input, Providers, [Processors, :differing_name], [Formatters::Output, :custom_output]
end
Sorry, this probably out of scope for this post but couldn't help myself when I saw somthing interesting to code. Also I'm not sure if
PROVIDER_NAMEwill be picked up in the local context of theAPIclass whenPipelineis included so you might need to code a mechanism for setting and retrieval that does but you get the idea.
conclusion
By separating your external dependency into it's logic that is then called by your API, you can then keep your API constant between versions and then the only difference between versions becomes the dependencies, without any breaking changes between them.
p.s. I completly admit to not having checked or tested any of this code as it was just examples to get the ball rolling.
 

 
    
Top comments (2)
You're not wrong, I would normally add insurance to insure that the output or the next output is returned but as I said at the bottom of my post, I never actually tested the code and was never intended to be properly used, merely ideas and examples to get the ball rolling, which is why I made it simple!
If someone is interested in a functional pipline example: