سؤال

We have a dependency to a third-party service which exposes a gigantic interface of which we only need like 3 methods. Additionally, the interface changes frequently...

I've decided to wrap the interface in a class in our project and only expose the methods that we need.

But I'm unsure how I should handle the return values... The interface returns an object of type Storage. We internally have a type StorageModel which is our internal representation of a Storage.

What would you return in the mapper: Storage or StorageModel? We have a DataService StorageService which get a dependency of the wrapper injected.

Currently I'm doing it basically like this:

public class StorageService 
{
    private readonly IExternalStorageWrapper externalStorageWrapper;

    public StorageService(IExternalStorageWrapper externalStorageWrapper)
    {
        this.externalStorageWrapper = externalStorageWrapper;
    }

    public StorageModel GetStorage(int storageId)
    {
        return this.externalStorageWrapper.GetStorage(storageId).ConvertToStorageModel();
    }
}

public class ExternalStorageWrapper : IExternalStorageWrapper
{
    public Storage GetStorage(int storageId)
    {
        using(var ext = new ExternalStorage())
        {
            return ext.GetStorage(storageId);
        }
    }
}

What would you say:

  • Is it good like above, that the wrapper returns the external Storage object and the internal StorageService returns the internal StorageModel?
  • Or would you return a StorageModel in the wrapper already?
هل كانت مفيدة؟

المحلول

In my optinion, the wrapper should deal with all things related to the external library. This means that the public interface of the Wrapper must not name any external types.

Mapping external types to the respective types of your application is part of the wrapper's duties. If this is not a trivial operation, then you may use the various tools available to decompose the problem, e.g. injecting a translator object. However, the translator must still be part of your wrapper module, and no other part of your application may depend on it.

This way, the rest of your application is completely immune not only to changes in the library, but also to replacements of the library for another one.

نصائح أخرى

I've decided to wrap the interface in a class in our project and only expose the methods that we need.

That's ok. This is also known as Adapter.

You choose the Adapter pattern, so the aim here is transforming one interface (library model) into another (domain model). So, if something from the former reaches the domain model, the adapter is failing to its purpose.

According to the previous arguments, the adapter should return the StorageModel.

Ultimately, your domain "speaks" a specific language, where Storage is a stranger.

But I'm unsure how I should handle the return values...

The key here is to know for what reason you are wrapping/adapting the library.

Adapter, Decorator, Facade patterns might have similarities but they are fairly different. As different as the problems they solve.

That said, you might be also interested in:

You can't effectively wrap a library by duplicating it.

What you should wrap is you usage of the library and that means not exposing it objects, in this case Storage. Don't try to duplicate them either.

Use the library, but keep it contained. So in your case, assuming your using the StorageService to store things you should wrap it in repositories

MyPocoObjectRepo
    MyPocoObject GetObject(string id);

where MyPocoObject is entirely your data and business logic. Not a duplication of Storage or a DataReader or anything

The answer is that it depends on whether or not you ever need to access Storage directly from a class that isn't StorageModel.

If you're going to wrap the library, it makes sense to also wrap the object returned by it to let you future proof changes made by the library in the future. However if you ever need to use Storage directly, it means there may be need to return Storage according to the situation. An argument could be said for obliging Storage usage here to be StorageModel as you likely want to remain consistent throughout your program.

I would strongly recommend you wrap both the interface and the returned object if you're not already doing so, though again, that only makes sense if you're only using StorageModel throughout your program and not Storage.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى softwareengineering.stackexchange
scroll top