REST还是SOAP?实体或运营?

时间:2012-09-21 15:04:24

标签: c# web-services rest soap

好的,我们假设我有一个名为Foo的实体的API,它看起来像这样:

.../api/foo (GET, PUT, POST) or (SELECT, INSERT, UPDATE internally)

这很适合很多消费者,尤其是移动设备,因为它非常简洁轻巧。现在让我们假设,如果我们坚持使用REST,那么名为ComeForth的操作就会存在,它看起来像这样:

.../api/foo/1/comeforth (POST) or (perform the come forth operation)

好的,所以我们已经有了,但是现在让我们假设我需要消费者对该操作的更多信息,所以为了保持简洁,我只是要构建一个保存{{1 ID和其他一些名为Foo的信息和API现在看起来像这样:

ComeForth

现在,以前的API .../api/comeforth (POST) or (perform the come forth operation) 对我来说似乎没问题,但第二个感觉就像是我试图将一个方形钉固定在一个圆孔中,只是因为我可以随心所欲地创造资源而不是'我的意思是我应该。所以,我的问题是:

  1. 我真的应该为.../api/foo/1/comeforth操作发布基于SOAP的服务吗?
  2. 如果我确实使用SOAP,那么这对消费者没有影响(即对于像ComeForth或移动设备这样的消费者来说还有很多工作要做吗?
  3. SOAP是基于操作的,而REST是基于实体的,这真的是一个硬性规则吗?如果是这样,那么即使JavaScript的API也会破坏该规则不会吗?
  4. 无论如何,我只是想确保我正在使用合适的技术来满足需求。

2 个答案:

答案 0 :(得分:2)

在您描述的情况下,正在操作的资源不是Foo,而是一个Transaction(基于您的评论)。您为特定操作类型(ComeForth)针对T(Foo)类型的实体建模长时间运行的事务。

控制器接受处理的事务POST请求,并返回事务的表示,其中包括分配给事务的唯一标识符,可用于跟踪其进度。

客户端执行GET操作以使用在接受处理事务时收到的唯一标识符来检索长时间运行的事务的状态。

我选择使用XML序列化进行演示,但您可以将参与事务的实体序列化为字节数组或任何有意义的事物。

示例Web API:

/交易/ {ID}

  • POST:创建新事务以进行处理
  • GET:检索具有指定ID的事务以验证是否存在 已完成

Web API服务模型:

[DataContract()]
public class Transaction
{
    public Transaction()
    {
        this.Id = Guid.Empty;
    }

    /// <summary>
    /// Gets or sets the unique identifier for this transaction.
    /// </summary>
    /// <value>
    /// A <see cref="Guid"/> that represents the unique identifier for this transaction.
    /// </value>
    [DataMember()]
    public Guid Id
    {
        get;
        set;
    }

    /// <summary>
    /// Gets or sets a value indicating if this transaction has been completed.
    /// </summary>
    /// <value>
    /// <see langword="true"/> if this transaction has been completed; otherwise, <see langword="false"/>.
    /// </value>
    [DataMember()]
    public bool IsComplete
    {
        get;
        set;
    }

    /// <summary>
    /// Gets or sets the action being performed.
    /// </summary>
    /// <value>The action being performed.</value>
    [DataMember()]
    public string Action
    {
        get;
        set;
    }

    /// <summary>
    /// Gets or sets the serialized representation of the entity participating in the transaction.
    /// </summary>
    /// <value>The serialized representation of the entity participating in the transaction.</value>
    [DataMember()]
    public string Entity
    {
        get;
        set;
    }

    /// <summary>
    /// Gets or sets the assembly qualified name of the entity participating in the transaction.
    /// </summary>
    /// <value>
    /// The <see cref="Type.AssemblyQualifiedName"/> of the <see cref="Entity"/>.
    /// </value>
    [DataMember()]
    public string EntityType
    {
        get;
        set;
    }

    /// <summary>
    /// Returns the <see cref="Entity"/> as a type of <typeparamref name="T"/>.
    /// </summary>
    /// <typeparam name="T">The type to project the <see cref="Entity"/> as.</typeparam>
    /// <returns>
    /// An object of type <typeparamref name="T"/> that represents the <see cref="Entity"/>.
    /// </returns>
    public T As<T>() where T : class
    {
        T result    = default(T);

        var serializer = new XmlSerializer(typeof(T));

        using (var reader = XmlReader.Create(new MemoryStream(Encoding.UTF8.GetBytes(this.Entity))))
        {
            result  = serializer.Deserialize(reader) as T;
        }

        return result;
    }

    /// <summary>
    /// Serializes the specified <paramref name="entity"/>.
    /// </summary>
    /// <typeparam name="T">The type of entity being serialized.</typeparam>
    /// <param name="entity">The entity to serialize.</param>
    public static Transaction From<T>(T entity, string action = null) where T : class
    {
        var transaction = new Transaction();

        transaction.EntityType  = typeof(T).AssemblyQualifiedName;
        transaction.Action      = action;

        var serializer  = new XmlSerializer(typeof(T));
        byte[] data     = null;

        using (var stream = new MemoryStream())
        {
            serializer.Serialize(stream, entity);
            stream.Flush();

            data        = stream.ToArray();
        }

        transaction.Entity = Encoding.UTF8.GetString(data);

        return transaction;
    }
}

[DataContract()]
public class Foo
{
    public Foo()
    {

    }

    [DataMember()]
    public string PropertyA
    {
        get;
        set;
    }

    [DataMember()]
    public int PropertyB
    {
        get;
        set;
    }

    [DataMember()]
    public Foo PropertyC
    {
        get;
        set;
    }
}

<强> TransactionsController:

public class TransactionsController : ApiController
{
    public TransactionsController() : base()
    {

    }

    private static ConcurrentDictionary<Guid, Transaction> _transactions = new ConcurrentDictionary<Guid, Transaction>();

    /// <summary>
    /// Using to initiate the processing of a transaction
    /// </summary>
    /// <param name="transaction"></param>
    /// <returns></returns>
    [HttpPost()]
    public HttpResponseMessage Post(Transaction transaction)
    {
        if(transaction == null)
        {
            return this.Request.CreateErrorResponse(HttpStatusCode.BadRequest, new HttpError("Unable to model bind request."));
        }

        transaction.Id  = Guid.NewGuid();

        // Execute asynchronous long running transaction here using the model.
        _transactions.TryAdd(transaction.Id, transaction);

        // Return response indicating request has been accepted fro processing
        return this.Request.CreateResponse<Transaction>(HttpStatusCode.Accepted, transaction);

    }

    /// <summary>
    /// Used to retrieve status of a pending transaction.
    /// </summary>
    /// <param name="id"></param>
    /// <returns></returns>
    [HttpGet()]
    public HttpResponseMessage Get(Guid id)
    {
        Transaction transaction = null;

        if(!_transactions.TryGetValue(id, out transaction))
        {
            return this.Request.CreateErrorResponse(HttpStatusCode.NotFound, new HttpError("Transaction does not exist"));
        }

        return this.Request.CreateResponse<Transaction>(HttpStatusCode.OK, transaction);
    }
}

示例客户端对事务控制器的调用:

var foo = new Foo()
{
    PropertyA   = "ABC",
    PropertyB   = 123,

    PropertyC   = new Foo()
    {
        PropertyA   = "DEF",
        PropertyB   = 456
    }
};

var transaction = Transaction.From<Foo>(foo, "ComeForth");

Guid pendingTransactionId = Guid.Empty;

// Initiate a transaction
using(var client = new HttpClient())
{
    client.BaseAddress  = new Uri("http://localhost:12775/api/", UriKind.Absolute);

    using (var response = client.PostAsJsonAsync<Transaction>("transactions", transaction).Result)
    {
        response.EnsureSuccessStatusCode();

        pendingTransactionId = response.Content.ReadAsAsync<Transaction>().Result.Id;
    }
}

// Retrieve status of transaction
Transaction pendingTransaction = null;

using (var client = new HttpClient())
{
    client.BaseAddress = new Uri("http://localhost:12775/api/", UriKind.Absolute);

    var requestUri = String.Format(null, "transactions\\{0}", pendingTransactionId.ToString());

    using (var response = client.GetAsync(requestUri).Result)
    {
        response.EnsureSuccessStatusCode();

        pendingTransaction = response.Content.ReadAsAsync<Transaction>().Result;
    }
}

// Check if transaction has completed
if(pendingTransaction.IsComplete)
{

}

因此,您仍然可以使用REST和ASP.NET Web API来模拟长时间运行的进程的启动,您只需将操作表示为其自己的独立资源即可。希望这有助于您的开发工作。

答案 1 :(得分:1)

对我而言,这听起来像一个非常开放的问题,需要考虑很多因素。

当您的呼叫与CRUD(创建,检索,更新,删除)匹配时,REST非常棒,以Twitter为例,您可以创建,回复,更新和删除Twitter帖子。

现在考虑一个处理交易的支付处理器,你可以创建一个(即通过一个cc#),它会有条件地做一些事情,然后可能返回一个交易结果(成功或失败)。您无法真正“更新”某个交易,而“转发”该交易并不会真正检索您发送的数据。您当然不能“删除”交易,您可能无效,或执行退款(部分或全部)。对于这个例子,REST没有意义。

这并不是说你不能混合使用REST和操作。某些实体符合REST,但是还有其他方法(例如处理付款),其中REST不适合。

选择REST或SOAP的决定(应该由目标受众决定,WCF服务(使用SOAP))在.NET中比REST实现起来要容易得多,如果消费技术是红宝石,反之亦然。