首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >休息还是肥皂?实体还是行动?

休息还是肥皂?实体还是行动?
EN

Stack Overflow用户
提问于 2012-09-21 15:04:24
回答 2查看 417关注 0票数 1

好的,让我们假设一下,我为一个名为Foo的实体提供了一个API,它看起来如下所示:

代码语言:javascript
复制
.../api/foo (GET, PUT, POST) or (SELECT, INSERT, UPDATE internally)

这对于很多消费者来说都很好,尤其是移动设备,因为它非常简洁和轻量级。现在让我们假设,如果我们坚持使用REST,那么存在一个名为ComeForth的操作,它看起来如下所示:

代码语言:javascript
复制
.../api/foo/1/comeforth (POST) or (perform the come forth operation)

好的,我们已经知道了,但是现在假设我需要消费者提供更多关于该操作的信息,所以为了保持简洁,我将构建一个新的资源来保存Foo ID和其他一些信息,名为ComeForth,而API现在看起来如下所示:

代码语言:javascript
复制
.../api/comeforth (POST) or (perform the come forth operation)

现在,对我来说,以前的API .../api/foo/1/comeforth似乎没问题,但是第二个API觉得我想把一个正方形的钉放进一个圆孔里,仅仅因为我可以随意创建资源,但这并不意味着我应该这么做。所以,我的问题是:

  1. 实际上,我应该为ComeForth操作发布一个基于SOAP的服务吗?
  2. 如果我确实使用了SOAP,那不是对消费者有影响吗?(也就是说,对于JavaScript或移动设备这样的消费者来说,这是更多的工作吗?)
  3. SOAP是基于操作的,REST是基于实体的,这真的是一个艰难而快速的规则吗?如果是这样的话,那么即使是.../api/foo/1/comeforth的API也会违反这条规则,不是吗?

无论如何,我只是想确保我正在使用正确的技术来满足需求。

EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2012-09-21 19:31:14

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

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

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

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

示例Web:

/事务/{id}

  • POST:创建一个用于处理的新事务
  • GET:用指定的id检索事务,以验证它是否已完成。

Web服务模型:

代码语言:javascript
复制
[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:

代码语言:javascript
复制
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);
    }
}

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

代码语言:javascript
复制
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来建模一个长期运行的进程的启动,您只需要将操作表示为它自己的单独资源即可。希望这对您的开发工作有所帮助。

票数 2
EN

Stack Overflow用户

发布于 2012-09-21 15:15:38

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

当调用匹配CRUD (创建、检索、更新、删除)时,REST是很好的,以Twitter为例,您可以创建、检索、更新和删除twitter帖子。

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

这并不是说你不能有一个休息和操作的混合体。在某些实体符合REST的情况下,但是在REST不合适的地方还有其他方法(例如处理支付)。

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

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/12533166

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档