Создание HTTP-прокси для служб


У меня есть клиент, фабрика HTTP, который создает отдельный экземпляр HTTP-прокси для каждой из моих услуг, которые находятся в облаке. Фабрика создает каждый экземпляр на основе интерфейса, который реализует служба. Все прокси наследовать от базового класса называется DalServiceBaseClient Что вы увидите в моем коде. Мне не совсем нравится, как завод решает возвращаемый тип на основе интерфейса.

Вот упрощенная версия мой код:

Вот как я называю мою фабрику, чтобы создать один из возможных прокси:

var gatewayFactory = new GatewayFactory();
var masterClient = gatewayFactory.CreateMasterClient("someBaseAddress");

Здесь тот же самый метод в GatewayFactory:

public IMasterService CreateMasterClient(string baseAddress)
{
    return (MasterServiceClient)CreateHttpClient<IMasterService>($"{baseAddress}api/master/");
}

А вот частный метод Create в GatewayFactory где все волшебство происходит:

private DalServiceBaseClient CreateHttpClient<T>(string baseAddress)
{
    var httpClient = new HttpClient
    {
        BaseAddress = new Uri(baseAddress)
    };

    if (typeof(T) == typeof(IMasterService))
    {
        return new MasterServiceClient(httpClient);
    }
    if (typeof(T) == typeof(ISlaveService))
    {
        return new SlaveServiceClient(httpClient);
    }
    if (typeof(T) == typeof(IRoleService))
    {
        return new RoleServiceClient(httpClient);
    }

    // A lot more ifs..

    throw new ArgumentException("No type exists for the provided interface");
}

Поскольку есть много разных прокси мой метод заканчивает тем, что имело большое ifS и я не смог найти более элегантный способ сделать это.



1627
6
задан 20 марта 2018 в 01:03 Источник Поделиться
Комментарии
3 ответа

Создать стратегию , чтобы найти сопоставлены реализации интерфейса:

IDictionary<Type, Type> mappings; //Populated with all interface to implementation types

private DalServiceBaseClient CreateHttpClient<T>(string baseAddress) {
var httpClient = new HttpClient {
BaseAddress = new Uri(baseAddress)
};
var implementationType = mappings[typeof(T)];
if (implementationType == null)
throw new ArgumentException("No type exists for the provided interface");

return (DalServiceBaseClient)Activator.CreateInstance(implementationType, httpClient);
}

Обратите внимание, как использовать отражение для создания экземпляра реализации.

Сопоставление будет предварительно заполнено и может выглядеть как...

mappings = new Dictionary<Type, Type>();
mappings[typeof(IMasterService)] = typeof(MasterServiceClient);
mappings[typeof(ISlaveService)] = typeof(SlaveServiceClient);

//...other types;

Другой вариант, если не слишком увлечена, используя отражение, чтобы использовать фабрику делегата, который позволит более строго типизированных карт.

IDictionary<Type, Func<HttpClient, DalServiceBaseClient>> mappings; //Populated with all interface to implementation types

private DalServiceBaseClient CreateHttpClient<T>(string baseAddress) {
var httpClient = new HttpClient {
BaseAddress = new Uri(baseAddress)
};
var implementationFactory = mappings[typeof(T)];
if (implementationFactory == null)
throw new ArgumentException("No type exists for the provided interface");

return implementationFactory(httpClient);
}

Которое могло бы быть заполнено как

mappings = new Dictionary<Type, Func<HttpClient, DalServiceBaseClient>>();
mappings[typeof(IMasterService)] = (httpClient) => new MasterServiceClient(httpClient);
mappings[typeof(ISlaveService)] = (httpClient) => new SlaveServiceClient(httpClient);

//...other types;

13
ответ дан 20 марта 2018 в 01:03 Источник Поделиться

Слишком много абстракции

Ваши публичные методы, например CreateMasterClientуже сказать вам, какой тип звонящего должен. При разработке личный способ, как вы есть, вы добавляете много окольных путей, что заставляет вас создавать странной логикой, просто передать информацию о том, какой тип нужен. Что вы получаете это, что вы централизовать создание HttpClient. Это не очень хороший компромисс.

Вместо этого используйте альтернативные способы централизации строительство HttpClientсоздать заводской способ для этого.

private HttpClient CreateHttpClient(string baseAddress)
{
return new HttpClient()
{
BaseAddress = new Uri(baseAddress)
};
}

И просто построить реальный объект непосредственно в открытый метод:

public IMasterService CreateMasterClient(string baseAddress)
{
return new MasterServiceClient(
CreateHttpClient($"{baseAddress}api/master/")
);
}

Можно обобщить это на любое другое общие параметры конструктора этих типов путем создания дополнительных методов.

Это значительно легче для другого разработчика, чтобы следовать. Не нужно обобщений. Нет необходимости в дополнительных забросов. Нет необходимости долго if заявления или условия в зависимости от типа параметров. Если ваше if блок на самом деле содержит больше логики (например, параметры, относящиеся к каждому типу или вообще выбрать другой тип для каждого интерфейса), это также хорошо отделяет эти части и делает его ясно, когда они на самом деле актуальны.

3
ответ дан 20 марта 2018 в 07:03 Источник Поделиться

Это прекрасный случай для инъекций зависимостей. Вам не нужно ifы, просто правильно настроенный контейнер. Вы можете легко сделать это с foreach петли и пусть Ди рамок делать тяжелую работу создания объектов.


Вот простой пример, как можно реализовать это с Autofac.


Мне нужно что-то для работы с так что я буду использовать этот двух интерфейсов...

interface IMasterServiceClient { }

interface ISlaveServiceClient { }

и их реализации:

class MasterServiceClient : IMasterServiceClient
{
public MasterServiceClient(HttpClient client)
{
Console.WriteLine($"{GetType().Name} initialized with {client.BaseAddress}");
}
}

class SlaveServiceClient : ISlaveServiceClient
{
public SlaveServiceClient(HttpClient client)
{
Console.WriteLine($"{GetType().Name} initialized with {client.BaseAddress}");
}
}

class ServiceClientUser
{
public ServiceClientUser(IMasterServiceClient masterServiceClient)
{
Console.WriteLine($"{GetType().Name} initialized");
}
}


Первым шагом является регистрация всех компонентов в контейнере. В этом случае вы можете использовать кортежи для определения интерфейсов, их реализациях и база адресов. Затем цикл по этому списку и регистрировать каждый компонент.

Внизу я также зарегистрировать одного из пользователей, который зависит от IMasterServiceClient.

IContainer InitializeContainer()
{
var builder = new ContainerBuilder();

var clients = new(Type InterfaceType, Type ImplementationType, string BaseAddress)[]
{
(typeof(IMasterServiceClient), typeof(MasterServiceClient), "http://foo/api/master/"),
(typeof(ISlaveServiceClient), typeof(SlaveServiceClient), "http://bar/api/slave/")
};

foreach (var client in clients)
{
builder
.RegisterType(client.ImplementationType)
.WithParameter(new TypedParameter(typeof(HttpClient), new HttpClient
{
BaseAddress = new Uri(client.BaseAddress)
}))
.As(client.InterfaceType)
// You only want to create it once and share the same instance among all components.
.InstancePerLifetimeScope();
}

builder
.RegisterType<ServiceClientUser>();

return builder.Build();
}


Второй и последний шаг это позволить Autofac построить все объекты для вас, так что вы можете просто Resolve он.

void Main()
{
using (var container = InitializeContainer())
using (var scope = container.BeginLifetimeScope())
{
var user = scope.Resolve<ServiceClientUser>();
}
}

Это будет печатать:

MasterServiceClient initialized with http://foo/
ServiceClientUser initialized


Имейте в виду, что это только простой пример. Вы можете сделать гораздо больше, чем это.

1
ответ дан 21 марта 2018 в 09:03 Источник Поделиться