Построение SOAP веб-сервисов, основанных на сообщениях, с помощью WCF

Original author: Sergey Morenko
  • Translation
  • Tutorial
WCF очень нравится мне как фрэймворк, упрощающий создание коммуникационного слоя. Но WCF's design style меня не устраивает. Я думаю, что создание нового метода для каждого DTO — это не самое хорошее решение, поэтому попытался решить эту проблему.

WCF имеет некоторые ограничения:
  • Не поддерживает перегрузку методов.
  • Не имеет универсального API.
  • Service Contract зависит от бизнес-требований.
  • Версионность должна выполняться на уровне DataContract и методов, имя операции должно быть универсальным.
  • Другие не .NET клиенты должны создавать столько клиентов, сколько сервисов у вас есть.

Я думаю, что подход в стиле RPC (Remote Procedure Call) не самый подходящий. Сервис должен быть повторно используемым, а влияние бизнес-требований на него должно быть минимальным. Я думаю, что удаленное API должно соответствовать следующим требованиям:
  • Обладать стабильным и универсальным интерфейсом.
  • Передавать данные в соответствии с паттерном DTO.

Веб-сервис, основанный на сообщениях, преодолевает большинство ограничений WCF путем добавления абстракции сообщения.
После прочтения статьи вы узнаете, как строить повторно используемые SOAP веб-сервисы, основанные на сообщениях (и перестанете постоянно плодить новые).

Дизайн веб-сервиса


Давайте взглянем на подход в стиле RPC, а также на подход, основанный на сообщениях (Message based).

Дизайн RPC


Главная идея стиля RPC — это дать клиентам возможность работать с удаленными сервисами как с локальными объектами. В WCF ServiceContract определяет операции, доступные на стороне клиента. Например:
[ServiceContract]
public interface IRpcService
{
    [OperationContract]
    void RegisterClient(Client client);

    [OperationContract]
    Client GetClientByName(string clientName);

    [OperationContract]
    List<Client> GetAllClients();
} 

Контракт сервиса очень прост и содержит три операции. Мы должны изменять клиент после любого изменения в контракте сервиса (например, добавления или удаления операции, изменения сигнатуры операции). Реальное приложение может иметь более чем 10 операций, поэтому сопровождение сервиса и клиентов является очень трудоемким.

Дизайн, основанный на сообщениях


В основе подхода, основанного на сообщениях, лежат паттерны Data Transfer Object и Gateway. DTO содержит все необходимые для коммуникации данные, а Gateway изолирует приложение от процесса коммуникации. Так сервис основанный на сообщениях получает сообщение-запрос и возвращает сообщение-ответ. Рассмотрим пример от API Amazon.

Пример запроса:
https://ec2.amazonaws.com/?Action=AllocateAddress
Domain=vpc
&AUTHPARAMS

Пример ответа:
<AllocateAddressResponse xmlns="http://ec2.amazonaws.com/doc/2013-02-01/">
   <requestId>59dbff89-35bd-4eac-99ed-be587EXAMPLE</requestId> 
   <publicIp>198.51.100.1</publicIp>
   <domain>vpc</domain>
   <allocationId>eipalloc-5723d13e</allocationId>
</AllocateAddressResponse>

Таким образом, контракт сервиса должен выглядеть примерно так:
public interface IMessageBasedService
{
    Response Execute(Request request);
} 
где Request и Response могут быть любыми DTO, то есть одним методом мы можем заменить любой RPC контракт сервиса, но WCF использует стиль RPC.

Стиль, основанный на сообщениях


Как вы уже знаете, для веб-сервиса, основанного на сообщениях, мы можем использовать объекты Request и Response для передачи любого DTO. Но WCF не поддерживает такой дизайн. Все внутренности коммуникаций в WCF основаны на использовании класса Message. То есть WCF конвертирует любой DTO в экземпляр Message и отправляет Message от клиента серверу. Поэтому мы должны использовать класс Message для объектов Request и Response.
Следующий контракт сервиса описывает коммуникацию с объектом Response и без него.
[ServiceContract]
public interface ISoapService
{
    [OperationContract(Action = ServiceMetadata.Action.ProcessOneWay)]
    void ProcessOneWay(Message message);

    [OperationContract(Action = ServiceMetadata.Action.Process,
        ReplyAction = ServiceMetadata.Action.ProcessResponse)]
    Message Process(Message message);
} 
ISoapService позволяет нам передавать любые данные, но этого не достаточно. Мы хотим создавать, удалять объекты и выполнять методы на нем. Что касается меня, лучший выбор — это CRUD-операции на объекте, так мы можем реализовать любую операцию. Прежде всего, давайте создадим SoapServiceClient, который сможет отправлять и получать любой DTO.

Soap service client


SoapServiceClient покажет, как создать Message из любого DTO. SoapServiceClient — это враппер, который конвертирует любой DTO в Message и отправляет его сервису. Отправляемое сообщение содержит следующие данные:
  • DTO
  • Тип DTO, необходимый для десериализации на стороне сервера
  • Метод, который будет вызван на стороне сервера.
Наша цель — создать повторно используемый клиент для SOAP веб-сервиса, который сможет отправлять/получать любой запрос/ответ и выполнять любые операции над объектом. Как упоминалось ранее — лучше всего для этого подходит CRUD, поэтому клиент может выглядеть примерно так:
var client = new SoapServiceClient("NeliburSoapService");

ClientResponse response = client.Post<ClientResponse>(createRequest);

response = client.Put<ClientResponse>(updateRequest);

Ниже представлен весь код метода Post класса SoapServiceClient.
public TResponse Post<TResponse>(object request)
{
    return Send<TResponse>(request, OperationTypeHeader.Post);
}

private TResponse Send<TResponse>(object request, MessageHeader operationType)
{
    using (var factory = new ChannelFactory<ISoapService>(_endpointConfigurationName))
    {
        MessageVersion messageVersion = factory.Endpoint.Binding.MessageVersion;
        Message message = CreateMessage(request, operationType, messageVersion);
        ISoapService channel = factory.CreateChannel();
        Message result = channel.Process(message);
        return result.GetBody<TResponse>();
    }
}

private static Message CreateMessage(
    object request, MessageHeader actionHeader, MessageVersion messageVersion)
{
    Message message = Message.CreateMessage(
        messageVersion, ServiceMetadata.Operations.Process, request);
    var contentTypeHeader = new ContentTypeHeader(request.GetType());
    message.Headers.Add(contentTypeHeader);
    message.Headers.Add(actionHeader);
    return message;
} 
Обратите, пожалуйста, внимание на метод CreateMessage и на то, как тип DTO и вызываемый метод добавляются через contentTypeHeader and actionHeader.
SoapContentTypeHeader и SoapOperationTypeHeader практически идентичны. The SoapContentTypeHeader используется для передачи типа DTO, а SoapOperationTypeHeader — для передачи целевой операции. Меньше слов, больше кода:
internal sealed class SoapContentTypeHeader : MessageHeader
{
    private const string NameValue = "nelibur-content-type";
    private const string NamespaceValue = "http://nelibur.org/" + NameValue;
    private readonly string _contentType;

    public SoapContentTypeHeader(Type contentType)
    {
        _contentType = contentType.Name;
    }

    public override string Name
    {
        get { return NameValue; }
    }

    public override string Namespace
    {
        get { return NamespaceValue; }
    }

    public static string ReadHeader(Message request)
    {
        int headerPosition = request.Headers.FindHeader(NameValue, NamespaceValue);
        if (headerPosition == -1)
        {
            return null;
        }
        var content = request.Headers.GetHeader<string>(headerPosition);
        return content;
    }

    protected override void OnWriteHeaderContents(XmlDictionaryWriter writer, MessageVersion messageVersion)
    {
        writer.WriteString(_contentType);
    }
}

Ниже представлены методы SoapServiceClient:
public static TResponse Get<TResponse>(object request)

public static Task<TResponse> GetAsync<TResponse>(object request)

public static void Post(object request)

public static Task PostAsync(object request)

public static TResponse Post<TResponse>(object request)

public static Task<TResponse> PostAsync<TResponse>(object request)

public static void Put(object request)

public static Task PutAsync(object request)
 
public static TResponse Put<TResponse>(object request)

public static Task<TResponse> PutAsync<TResponse>(object request)

public static void Delete(object request)

public static Task DeleteAsync(object request)

Как вы уже заметили, все CRUD операции имеют асинхронные версии.

SOAP сервис


SOAP сервис должен уметь:
  • Создать конкретный Request из Message
  • Вызвать целевой метод на Request
  • При необходимости создать и вернуть Message из Response


Наша цель — создать что-то такое, что будет вызывать подходящий CRUD-метод для конкретного Request. В примере ниже показано, как можно добавлять и получать объект Client (клиента).

public sealed class ClientProcessor : IPut<CreateClientRequest>, 
    IGet<GetClientRequest>
{
    private readonly List<Client> _clients = new List<Client>();

    public object Get(GetClientRequest request)
    {
        Client client = _clients.Single(x => x.Id == request.Id);
        return new ClientResponse {Id = client.Id, Name = client.Name};
    }

    public object Put(CreateClientRequest request)
    {
        var client = new Client
            {
                Id = Guid.NewGuid(),
                Name = request.Name
            };
        _clients.Add(client);
        return new ClientResponse {Id = client.Id};
    }
}


Наибольший интерес представляют интерфейсы IGet и IPost. Они представляют операции CRUD. Взглянем на диаграмму классов:
диаграмма классов
Теперь необходимо связать Request с соответствующей операцией CRUD. Самый простой путь — связать Request с обработчиком запросов (request Processor). За эту функциональность отличает NeliburService. Давайте взглянем на него.
public abstract class NeliburService
{
    internal static readonly RequestMetadataMap _requests = new RequestMetadataMap();
    protected static readonly Configuration _configuration = new Configuration();
    private static readonly RequestProcessorMap _requestProcessors = new RequestProcessorMap();

    protected static void ProcessOneWay(RequestMetadata requestMetaData)
    {
        IRequestProcessor processor = _requestProcessors.Get(requestMetaData.Type);
        processor.ProcessOneWay(requestMetaData);
    }

    protected static Message Process(RequestMetadata requestMetaData)
    {
        IRequestProcessor processor = _requestProcessors.Get(requestMetaData.Type);
        return processor.Process(requestMetaData);
    }

    protected sealed class Configuration : IConfiguration
    {
        public void Bind<TRequest, TProcessor>(Func<TProcessor> creator)
            where TRequest : class
            where TProcessor : IRequestOperation
        {
            if (creator == null)
            {
                throw Error.ArgumentNull("creator");
            }
            _requestProcessors.Add<TRequest, TProcessor>(creator);
            _requests.Add<TRequest>();
        }

        public void Bind<TRequest, TProcessor>()
            where TRequest : class
            where TProcessor : IRequestOperation, new()
        {
            Bind<TRequest, TProcessor>(() => new TProcessor());
        }
    }
}

RequestMetadataMap используется для хранения типа объекта Request, который требуется для создания конкретного Request из Message.
internal sealed class RequestMetadataMap
{
    private readonly Dictionary<string, Type> _requestTypes =
        new Dictionary<string, Type>();

    internal void Add<TRequest>()
        where TRequest : class
    {
        Type requestType = typeof(TRequest);
        _requestTypes[requestType.Name] = requestType;
    }

    internal RequestMetadata FromRestMessage(Message message)
    {
        UriTemplateMatch templateMatch = WebOperationContext.Current.IncomingRequest.UriTemplateMatch;
        NameValueCollection queryParams = templateMatch.QueryParameters;
        string typeName = UrlSerializer.FromQueryParams(queryParams).GetTypeValue();
        Type targetType = GetRequestType(typeName);
        return RequestMetadata.FromRestMessage(message, targetType);
    }

    internal RequestMetadata FromSoapMessage(Message message)
    {
        string typeName = SoapContentTypeHeader.ReadHeader(message);
        Type targetType = GetRequestType(typeName);
        return RequestMetadata.FromSoapMessage(message, targetType);
    }

    private Type GetRequestType(string typeName)
    {
        Type result;
        if (_requestTypes.TryGetValue(typeName, out result))
        {
            return result;
        }
        string errorMessage = string.Format(
            "Binding on {0} is absent. Use the Bind method on an appropriate NeliburService", typeName);
        throw Error.InvalidOperation(errorMessage);
    }
}

RequestProcessorMap cсвязывает тип объекта Request с обработчиком.
internal sealed class RequestProcessorMap
{
    private readonly Dictionary<Type, IRequestProcessor> _repository =
        new Dictionary<Type, IRequestProcessor>();

    public void Add<TRequest, TProcessor>(Func<TProcessor> creator)
        where TRequest : class
        where TProcessor : IRequestOperation
    {
        Type requestType = typeof(TRequest);
        IRequestProcessor context = new RequestProcessor<TRequest, TProcessor>(creator);
        _repository[requestType] = context;
    }

    public IRequestProcessor Get(Type requestType)
    {
        return _repository[requestType];
    }
}

Теперь мы готовы для последнего шага: вызова целевого метода. Вот наш SOAP-сервис:
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)]
public sealed class SoapService : ISoapService
{
    public Message Process(Message message)
    {
        return NeliburSoapService.Process(message);
    }

    public void ProcessOneWay(Message message)
    {
        NeliburSoapService.ProcessOneWay(message);
    }
}  

Прежде всего давайте посмотрим на диаграмму последовательности, описывающую процесс выполнения на стороне сервиса.
диаграмма последовательности
Давайте погрузимся в код шаг за шагом. NeliburSoapService просто выполняет другой код, взглянем на него.
public sealed class NeliburSoapService : NeliburService
{
    private NeliburSoapService()
    {
    }

    public static IConfiguration Configure(Action<IConfiguration> action)
    {
        action(_configuration);
        return _configuration;
    }

    public static Message Process(Message message)
    {
        RequestMetadata metadata = _requests.FromSoapMessage(message);
        return Process(metadata);
    }

    public static void ProcessOneWay(Message message)
    {
        RequestMetadata metadata = _requests.FromSoapMessage(message);
        ProcessOneWay(metadata);
    }
}

NeliburSoapService просто декорирует RequestMetadataMap, то есть вызывает соответствующий метод для создания RequestMetadata для SOAP Message.
Самое интересное происходит здесь:
  • RequestMetadata requestMetaData = _requests.FromSoapMessage(message)
    
  • context.Process(requestMetaData).
    

SoapRequestMetadata — это главный объект, который соединяет в себе тип операции CRUD, данные запроса (Request), его тип, а также может отвечать на запрос.
internal sealed class SoapRequestMetadata : RequestMetadata
{
    private readonly MessageVersion _messageVersion;
    private readonly object _request;

    internal SoapRequestMetadata(Message message, Type targetType) : base(targetType)
    {
        _messageVersion = message.Version;
        _request = CreateRequest(message, targetType);
        OperationType = SoapOperationTypeHeader.ReadHeader(message);
    }

    public override string OperationType { get; protected set; }

    public override Message CreateResponse(object response)
    {
        return Message.CreateMessage(_messageVersion, SoapServiceMetadata.Action.ProcessResponse, response);
    }

    public override TRequest GetRequest<TRequest>()
    {
        return (TRequest)_request;
    }

    private static object CreateRequest(Message message, Type targetType)
    {
        using (XmlDictionaryReader reader = message.GetReaderAtBodyContents())
        {
            var serializer = new DataContractSerializer(targetType);
            return serializer.ReadObject(reader);
        }
    }
}

А в конце мы просто вызываем соответствующую CRUD-операцию через RequestProcessor. RequestProcessor использует RequestMetadata для определения операции и вызывает ее, когда возвращает результат классу SoapServiceClient.
internal sealed class RequestProcessor<TRequest, TProcessor> : IRequestProcessor
    where TRequest : class
    where TProcessor : IRequestOperation
{
    private readonly Func<TProcessor> _creator;

    public RequestProcessor(Func<TProcessor> creator)
    {
        _creator = creator;
    }

    public Message Process(RequestMetadata metadata)
    {
        switch (metadata.OperationType)
        {
            case OperationType.Get:
                return Get(metadata);
            case OperationType.Post:
                return Post(metadata);
            case OperationType.Put:
                return Put(metadata);
            case OperationType.Delete:
                return Delete(metadata);
            default:
                string message = string.Format("Invalid operation type: {0}", metadata.OperationType);
                throw Error.InvalidOperation(message);
        }
    }

    public void ProcessOneWay(RequestMetadata metadata)
    {
        switch (metadata.OperationType)
        {
            case OperationType.Get:
                GetOneWay(metadata);
                break;
            case OperationType.Post:
                PostOneWay(metadata);
                break;
            case OperationType.Put:
                PutOneWay(metadata);
                break;
            case OperationType.Delete:
                DeleteOneWay(metadata);
                break;
            default:
                string message = string.Format("Invalid operation type: {0}", metadata.OperationType);
                throw Error.InvalidOperation(message);
        }
    }

    private Message Delete(RequestMetadata metadata)
    {
        var service = (IDelete<TRequest>)_creator();
        var request = metadata.GetRequest<TRequest>();
        object result = service.Delete(request);
        return metadata.CreateResponse(result);
    }

    private void DeleteOneWay(RequestMetadata metadata)
    {
        var service = (IDeleteOneWay<TRequest>)_creator();
        var request = metadata.GetRequest<TRequest>();
        service.DeleteOneWay(request);
    }

    private Message Get(RequestMetadata metadata)
    {
        var service = (IGet<TRequest>)_creator();
        var request = metadata.GetRequest<TRequest>();
        object result = service.Get(request);
        return metadata.CreateResponse(result);
    }

    private void GetOneWay(RequestMetadata metadata)
    {
        var service = (IGetOneWay<TRequest>)_creator();
        var request = metadata.GetRequest<TRequest>();
        service.GetOneWay(request);
    }

    private Message Post(RequestMetadata metadata)
    {
        var service = (IPost<TRequest>)_creator();
        var request = metadata.GetRequest<TRequest>();
        object result = service.Post(request);
        return metadata.CreateResponse(result);
    }

    private void PostOneWay(RequestMetadata metadata)
    {
        var service = (IPostOneWay<TRequest>)_creator();
        var request = metadata.GetRequest<TRequest>();
        service.PostOneWay(request);
    }

    private Message Put(RequestMetadata metadata)
    {
        var service = (IPut<TRequest>)_creator();
        var request = metadata.GetRequest<TRequest>();
        object result = service.Put(request);
        return metadata.CreateResponse(result);
    }

    private void PutOneWay(RequestMetadata metadata)
    {
        var service = (IPutOneWay<TRequest>)_creator();
        var request = metadata.GetRequest<TRequest>();
        service.PutOneWay(request);
    }
}

Демонстрационный пример


Прежде всего, объявим data contracts:
  • CreateClientRequest — запрос на создание нового клиента
  • UpdateClientRequest — запрос на обновление email клиента
  • GetClientRequest — запрос на получение клиента по id
  • ClientResponse — информация о клиенте
  • RemoveClientRequest — запрос на удаление клиента


Server's side


Конфигурационный файл самый обычный:
<configuration>

    <!--WCF-->
    <system.serviceModel>
        <services>
            <service name="Nelibur.ServiceModel.Services.Default.SoapServicePerCall">
                <endpoint address="http://localhost:5060/service" binding="basicHttpBinding"
                          bindingConfiguration="ServiceBinding"
                          contract="Nelibur.ServiceModel.Contracts.ISoapService" />
            </service>
        </services>
        <bindings>
            <basicHttpBinding>
                <binding name="ServiceBinding">
                    <security mode="None">
                        <transport clientCredentialType="None" />
                    </security>
                </binding>
            </basicHttpBinding>
        </bindings>
    </system.serviceModel>

    <startup>
        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
    </startup>
</configuration>


WCF-сервис чрезвычайно прост:
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)]
public sealed class SoapServicePerCall : ISoapService
{
    /// <summary>
    ///     Process message with response.
    /// </summary>
    /// <param name="message">Request message.</param>
    /// <returns>Response message.</returns>
    public Message Process(Message message)
    {
        return NeliburSoapService.Process(message);
    }

    /// <summary>
    ///     Process message without response.
    /// </summary>
    /// <param name="message">Request message.</param>
    public void ProcessOneWay(Message message)
    {
        NeliburSoapService.ProcessOneWay(message);
    }
}

Привязка всех запросов к обработчикам. Для простоты я создал только один обработчик запросов. Вы можите сделать столько запросов, сколько захотите. Советую почитать статью Мартина Фаулера о CQRS. Это поможет вам сделать правильный выбор. Код связи запросов и обработчиков:
private static void BindRequestToProcessors()
{
    NeliburSoapService.Configure(x =>
        {
            x.Bind<CreateClientRequest, ClientProcessor>();
            x.Bind<UpdateClientRequest, ClientProcessor>();
            x.Bind<DeleteClientRequest, ClientProcessor>();
            x.Bind<GetClientRequest, ClientProcessor>();
        });
}

И, наконец, ClientProcessor:
public sealed class ClientProcessor : IPost<CreateClientRequest>,
                                        IGet<GetClientRequest>,
                                        IDeleteOneWay<DeleteClientRequest>,
                                        IPut<UpdateClientRequest>
{
    private static List<Client> _clients = new List<Client>();

    public void DeleteOneWay(DeleteClientRequest request)
    {
        Console.WriteLine("Delete Request: {0}\n", request);
        _clients = _clients.Where(x => x.Id != request.Id).ToList();
    }

    public object Get(GetClientRequest request)
    {
        Console.WriteLine("Get Request: {0}", request);
        Client client = _clients.Single(x => x.Id == request.Id);
        return new ClientResponse { Id = client.Id, Email = client.Email };
    }

    public object Post(CreateClientRequest request)
    {
        Console.WriteLine("Post Request: {0}", request);
        var client = new Client
        {
            Id = Guid.NewGuid(),
            Email = request.Email
        };
        _clients.Add(client);
        return new ClientResponse { Id = client.Id, Email = client.Email };
    }

    public object Put(UpdateClientRequest request)
    {
        Console.WriteLine("Put Request: {0}", request);
        Client client = _clients.Single(x => x.Id == request.Id);
        client.Email = request.Email;
        return new ClientResponse { Id = client.Id, Email = client.Email };
    }
}


Client's side


Код клиента прост:
private static void Main()
{
    var client = new SoapServiceClient("NeliburSoapService");

    var createRequest = new CreateClientRequest
        {
            Email = "email@email.com"
        };
    Console.WriteLine("POST Request: {0}", createRequest);
    ClientResponse response = client.Post<ClientResponse>(createRequest);
    Console.WriteLine("POST Response: {0}\n", response);

    var updateRequest = new UpdateClientRequest
        {
            Email = "new@email.com",
            Id = response.Id
        };

    Console.WriteLine("PUT Request: {0}", updateRequest);
    response = client.Put<ClientResponse>(updateRequest);
    Console.WriteLine("PUT Response: {0}\n", response);

    var getClientRequest = new GetClientRequest
        {
            Id = response.Id
        };
    Console.WriteLine("GET Request: {0}", getClientRequest);
    response = client.Get<ClientResponse>(getClientRequest);
    Console.WriteLine("GET Response: {0}\n", response);

    var deleteRequest = new DeleteClientRequest
        {
            Id = response.Id
        };
    Console.WriteLine("DELETE Request: {0}", deleteRequest);
    client.Delete(deleteRequest);

    Console.ReadKey();
}


Результаты выполнения:
клиент:
клиент

сервис:
сервис

Вот и все


Я надеюсь, что вам понравилось. Здесь вы можете узнать, как строить RESTful веб-сервисы на WCF и Nelibur. Спасибо, что прочли статью (перевод). Исходники можно скачать со страницы оригинала или с GitHub.
Ads
AdBlock has stolen the banner, but banners are not teeth — they will be back

More

Comments 25

    0
    (что, опять?)

    Пожалуйста, объясните, что вы имеете в виду под «Сервис должен быть повторно используемым». Какая именно часть сервиса должна быть повторно используемой и зачем?

    Каким образом в этой реализации предполагается обмен метаданными и их валидация?
      0
      Обмен мета-данными, предположу:
      Нет конкретных объектов в методах сервиса, нет атрибутов включающих их на уровне родителя, нет GetKnowTypes. Никак.
      Универсальность в SOA, в таком виде, для большинства проектов, сильна сомнительна.
      По поводу контрактов операций, то количество операций там не должно превышать 3-5, это эмпирически.

      Подход имеет право на жизнь, когда пишется иерархия Message и его наследников, после чего через специальные вызовы или атрибуты успешно реализуется мета-информация, а в методе исполнения фабрика обработки запросов. Делали так для игровых проектов, где много небольших событий, вроде бы все похожи, но чуть-чуть да разные.
        0
        Вот и мне кажется, что формализованного обмена метаданными тут нет, со всеми вытекающими отсюда проблемами. Это не «стабильный и универсальный интерфейс», это «отсутствие формального интерфейса».
          0
          Тут видимо человек предполагает, что у него все проекты — однотипные, сообщения он и получает и отправляет. Т.е. это всегда команда, у которых один source control и т.д. Мне кажется, в этом есть своя прелесть, но я бы все равно реализовывал через Message и наследование. С другой стороны, а зачем тогда вообще использовать WCF? SOA это ведь отдельные независимые сервисы, которые хоть как-то миру сообщают, кто они и что они.
            0
            Ну, скажем, SOA-то никто и не заявлял. Но ценность подобного подхода мне и правда не понятна; по крайней мере, я так и не смог найти сценария, где от него был бы существенный выигрыш.
            • UFO just landed and posted this here
                0
                Не, ServiceStack — не интересно. У них реализация такова, что внутри — message-based, а снаружи можно выставить типизованный контракт тем не менее.

                Ну и достоинства они описывают, прямо скажем, не message-based, а своего «гениального» стека. Потому что вообще-то WCF тоже message-based, поэтому было бы сложно так выделиться.

                Так что вернемся к вопросу: каковы достоинства конкретного приведенного в этом посте подхода?
                • UFO just landed and posted this here
                    0
                    В линках, как я уже говорил, ничего особенного не написано. А своими словами вы изложить не можете?

                    А подход-то похож, да только какой в нем смысл, если есть ServiceStack, в котором реализовано нормально?
                    • UFO just landed and posted this here
                        0
                        прочитайте — готов обсудить,

                        Прочитал. Давайте обсуждать. В чем преимущества?

                        • UFO just landed and posted this here
                            0
                            Понимаете ли, в чем дело… я не понимаю, как статья относится к тому, что в посте. Статья полемизирует с неким абстрактным WCF-в-вакууме (хотя WCF прекрасно позволяет делать Message Contracts, в документации это описано). Пост же полемизирует вообще непонятно с чем, но зато предлагает выставлять сервисы, не имеющие метаданных.

                            Поэтому давайте вернемся к началу: какие именно преимущества из описанных в статье (назовите хотя бы три) реализуются подходом, описанным в статье, и при этом не реализуются обычными Message Contract в WCF?

                            При этом назвать недостаток у предложенного в посте подхода я могу легко: по описанному сервису невозможно получить типизованный контракт, что делает невозможной автоматическую генерацию клиентов (у ServiceStack этого недостатка нет, у них свой механизм формирования метаданых).

                            • UFO just landed and posted this here
                                0
                                Я так понимаю, что вы не можете сказать, какие преимущества подхода, описанного у ServiceStack, применимы к тому, что пишет автор поста?

                                (про достоинства самого сервис-стека я бы поговорил где-нибудь в другом месте, а приписывать их тому, что описано здесь — не надо)
                                • UFO just landed and posted this here
                                    0
                                    Окей, пойдем с другой стороны.

                                    Я хочу заметить, что plain vanilla WCF — он message-based. Правильно ли я понимаю, что все описанные «advantages of message based web services» к нему равно применимы? Если нет, то какие не применимы, и почему?

                                    (подход «похож» у всех WS-фреймворков, растущих из SOAP)
                                    • UFO just landed and posted this here
                                        0
                                        Во-первых, это банальное жульничество. Любой сервисный фреймворк на .net маппит операции на методы (включая service stack); при этом никто не мешает в WCF сделать сервис хоть с одной операцией, а аргументом этой операции все равно является сообщение.

                                        И это, собственно, приводит нас к вопросу: так какое именно достоинство из перечисленных в статье недоступно WCF?

                                        (ваша конкретная цитата вообще относится к предлагаемому разработчикам стилю письма, а не к реальным возможностям/недостаткам)
                                        • UFO just landed and posted this here
                                            +1
                                            Удивительно, что вы не видите: то, чего вы хотите — это и есть маппинг на методы; вы хотите от WCF, чтобы при приходе определенного сообщения вызывался определенный метод… это ли не маппинг на методы? И я не знаю фреймворка, в котором это было бы иначе, почему и говорю, что утверждение «ошибочное решение с маппингом на методы» — жульничество (особенно от создателей фреймворка, в котором названия методов определены соглашением).

                                            А теперь давайте, как вы говорите, «разбираться в технологиях».

                                            Описанное вами ограничение (уникальности именования) — это не особенность WCF, это требование WSDL (который, на минуточку, industry standard, краеугольный камень SOA и так далее). WSDL требует, чтобы операции в рамках одного port имели уникальные имена. И WSDL же говорит, что у одной операции ровно один тип входного сообщения. Как следствие, WCF — который изначально писался как WS*-совместимый фреймворк — налагает то же ограничение на реализуемые сервисы. Собственно говоря, вы сам допускаете ошибку «маппинга на методы», которую приписываете WCF — вы оперируете понятиями «метод» и «перегрузка», которых в SOA просто нет. Если бы вы мыслили в терминах стандартов, у вас бы не возникло таких странных требований.

                                            Но предположим у вас есть потребность в поведении, при котором одна и та же операция может получать на вход более одного разного типа данных, и обрабатывать их по-разному в зависимости от типа. Это поведение достижимо в WCF как минимум двумя разными способами, с различной степенью вмешательства в инфраструктуру.
                                            • UFO just landed and posted this here
                                                0
                                                ибо такие же ограничение есть и в REST на WCF

                                                REST — это совсем другое дело, не надо его сюда тащить. Пост — про SOAP.

                                                по больше части абсолютно все равно чье это ограничение.

                                                Отнюдь. Если это ограничение парадигмы, то надо менять парадигму, а не продолжать есть этот невкусный кактус.

                                                читайте статью про преимущества MessageBased подхода… :)

                                                Это не связанные вещи. Message-based прекрасно делается другими способами.

                                                По мне «операцию» лучше передавать «командной», ибо сам по себе метод например «Process» большого толку не имеет, смысл появляется если написать полную сигнатуру…

                                                Так это же вы выдумали этот метод, а не я. В контрактах моих сервисов таких методов не бывает.

                                                а если передаваемые данные представлять в виде команд или запросов, то повторноиспользуемость + полиморфизм сделают свое дело…

                                                Можете привести конкретный пример сценария, где это было бы выигрышно, и где реально работал бы полиморфизм и было повторное использование чего бы то ни было, и при этом тот же эффект не был бы достижим в plain WCF разумными усилиями?

                                                более подробно можно почитать здесь martinfowler.com/bliki/CQRS.html

                                                Омг, вот только CQRS сюда не привлекайте. Пойдите и прочитайте Грега Янга, там явно сказано: для CQRS не нужен messaging.
                                                • UFO just landed and posted this here
                                                    0
                                                    Смотрю как только арумент не подходит, сразу **** типа

                                                    Если аргумент не относится к теме — он действительно не подходит.

                                                    ServiceStack не интересно, потому что пост про WCF. REST не интересно, потому что пост про SOAP. И то, и другое написано в заголовке поста.

                                                    Я так понимаю, привести конкретный пример, запрошенный выше, вы не можете или не считаете нужным?

    Only users with full accounts can post comments. Log in, please.