如何为状态机或有限自动机实现 RESTful 资源

发布于 2024-10-31 06:45:35 字数 555 浏览 0 评论 0原文

我是 Rails 和 REST 新手,我正在尝试找出如何最好地公开由具有状态机(换句话说是有限自动机)的域对象支持的资源。

我见过许多使模型类成为状态机的 gem,例如 aasm、转换、工作流,但它们都没有记录如何在面向资源的控制器中实际使用它们的示例。它们似乎都暗示状态转换是由“事件”触发的,这实际上是一个方法调用。我对这意味着什么有一些疑问:

  1. 更新操作(PUT 方法)不合适,因为 PUT 被认为是幂等的。唯一可能的情况是状态作为表示的一部分发送。这与“事件”不一致。这是正确的吗?
  2. 由于事件不是幂等的,因此必须使用 POST。但是,到哪个资源呢?每个可能的事件都有一个子资源吗?或者,是否有一个(/updatestate)将要触发的事件以及该事件的任何参数作为其表示?
  3. 由于资源的状态是由另一个资源可能触发的事件修改的,因此创建操作是否应该接受对状态属性(或依赖于状态机的任何其他属性)的更改?
  4. [更新的问题] 在 UI 中公开过渡的好方法是什么?由于事件不是状态,因此允许更新状态属性(以及依赖于状态转换的任何其他属性)似乎没有意义。这是否意味着更新操作中应忽略这些属性?

I'm a Rails and REST newbie and I'm trying to figure how best to expose a resource that is backed by a domain object that has a state machine (in other words is a finite automata).

I've seen a number of gems for making a model class a state machine, such as aasm, transitions, workflow, but none of them document examples of how they are actually used in a resource oriented controller. They all seem to imply that state transitions are triggered by an "event" , which is really a method call. Some questions I have with what this implies are:

  1. The update action (PUT method) is not appropriate because PUT is suppose to be idempotent. The only this would be possible is if the state was sent as part of the representation. This is inconsistet with an "event". Is this correct?
  2. Since, events aren't idempotent, then the a POST must be used. But, to which resource? Is there a subresource for each possible event? Or, is there one (/updatestate) that takes as its representation the event to trigger and any parameters to the event?
  3. Since the state of the resource is modified by an event triggered potentially by another resource, should the create action accept changes to the state attribute (or any other attributes that are dependent on the state machine)?
  4. [Updated question] What is a good way to expose the transitions in the UI? Since events aren't states, it would seem that it doesn't make sense to allow the state attribute (and any other attribute that is dependent on state transitions) to be updated. Does that mean that these attributes should be ignored in the update action?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

月亮是我掰弯的 2024-11-07 06:45:35
  • 更新操作(PUT 方法)不合适,因为 PUT 被认为是幂等的。唯一可能的情况是状态作为表示的一部分发送。这与“事件”不一致。这是正确的吗?

正确的。

  • 由于事件不是幂等的,因此必须使用 POST。但是,到哪个资源呢?每个可能的事件都有一个子资源吗?或者,是否有一个(/updatestate)将要触发的事件以及该事件的任何参数作为其表示形式?

你可以用两种方法来做。您可以在同一个应用程序中支持这两者,事件类型的变化由传入文档或接收资源决定。就我个人而言,我更喜欢通过不同的文档类型来完成此操作,但这只是我的意见。如果您选择多个资源路线,请确保它们是可发现的(即,通过获取其父资源时返回的文档中描述的每个资源的链接)。

  • 由于资源的状态是由另一个资源可能触发的事件修改的,因此创建操作是否应该接受对状态属性(或依赖于状态机的任何其他属性)的更改?

由你决定;没有真正的理由让你必须密切关注创作的任何特定属性。 (您可以通过说状态在创建后立即更改为状态机的正确初始状态来合理化这一点。)在我完成的状态机中,无论​​如何创建都是通过 POST 进行的(并且是一个不同的 - 相当复杂的 -文档),所以整个事情没有实际意义,但如果您允许多个初始状态,那么在创建文档中采用“这是我首选的起始状态”提示是有意义的。需要明确的是,仅仅因为用户想要它并不意味着你必须这样做;而是因为用户想要它。当你拒绝用户的建议时,是否要向用户投诉是你的决定。

  • 列出项目

[库存答案。]

  • The update action (PUT method) is not appropriate because PUT is suppose to be idempotent. The only this would be possible is if the state was sent as part of the representation. This is inconsistet with an "event". Is this correct?

Correct.

  • Since, events aren't idempotent, then the a POST must be used. But, to which resource? Is there a subresource for each possible event? Or, is there one (/updatestate) that takes as its representation the event to trigger and any parameters to the event?

You can do it both ways. You can support both in the same application, with variation in event types being determined by either the incoming document or the receiving resource. Personally, I would prefer to do it by differing document types, but that's just my opinion. If you do go the multiple resources route, make sure they're discoverable (i.e., by having links to each of them described in the document returned when you GET their parent resource).

  • Since the state of the resource is modified by an event triggered potentially by another resource, should the create action accept changes to the state attribute (or any other attributes that are dependent on the state machine)?

Up to you; there's no real reason why you have to pay close attention to any particular attribute on creation. (You could rationalize this by saying that the state changes to a proper initial state for the state machine immediately after creation.) In the state machines I've done, the creation was by a POST anyway (and of a different – rather complex – document) so the whole thing was moot, but if you allow multiple initial states then it makes sense to take a “this is my preferred starting state” hint in the creation document. To be clear, just because the user wants it doesn't mean you have to do it; whether you want to complain to the user when you reject a suggestion of theirs is your call.

  • List item

[Stock answer.]

我们只是彼此的过ke 2024-11-07 06:45:35

参加聚会有点晚了,但我正在为自己研究这个确切的问题,发现我目前用来管理状态机的 gem (pluginaweek 的 state_machine)有一些方法可以很好地处理这个问题。

当与 ActiveRecord 一起使用时(我假设还有其他持久层),它提供了一个 #state_event= 方法,该方法接受您想要触发的事件的字符串表示形式。请参阅此处的文档。

# For example,

vehicle = Vehicle.create          # => #<Vehicle id: 1, name: nil, state: "parked">
vehicle.state_event               # => nil
vehicle.state_event = 'invalid'
vehicle.valid?                    # => false
vehicle.errors.full_messages      # => ["State event is invalid"]

vehicle.state_event = 'ignite'
vehicle.valid?                    # => true
vehicle.save                      # => true
vehicle.state                     # => "idling"
vehicle.state_event               # => nil

# Note that this can also be done on a mass-assignment basis:

vehicle = Vehicle.create(:state_event => 'ignite')  # => #<Vehicle id: 1, name: nil, state: "idling">
vehicle.state                                       # => "idling"

这使您可以简单地在资源的编辑表单中添加一个 state_event 字段,并像更新任何其他属性一样轻松地获得状态转换。

现在我们显然仍然使用 PUT 来使用这种方法来触发事件,这不是 RESTful。然而,gem 确实提供了一个有趣的示例,至少“感觉”相当 RESTful ,尽管它在幕后使用相同的非 RESTful 方法。

正如您可以在此处此处,gem 的内省功能允许您在表单中呈现您想要触发的事件该事件的结果状态的名称。

<div class="field">
  <%= f.label :state %><br />
  <%= f.collection_select :state_event, @user.state_transitions, :event, :human_to_name, :include_blank => @user.human_state_name %>
</div>

<div class="field">
  <%= f.label :access_state %><br />
  <%= f.collection_select :access_state_event, @user.access_state_transitions, :event, :human_event, :include_blank => "don't change" %>
</div>

使用后一种技术,您可以将模型状态简单地基于表单更新为任何有效的下一个状态,而无需编写任何额外的代码。从技术上讲,它不是 RESTful,但它允许您在 UI 中轻松地以这种方式呈现它。

这种技术的简洁性加上尝试将基于事件的状态机转换为简单的 RESTful 资源时固有的冲突足以让我满意,所以希望它也能为您提供一些见解。

A little late to the party here, but I was researching this exact issue for myself and found that the gem I'm currently using to manage my state machines (state_machine by pluginaweek) has some methods that deal with this issue quite nicely.

When used with ActiveRecord (and I'm assuming other persistence layers as well), it provides a #state_event= method that accepts a string representation of the event you would like to fire. See documentation here.

# For example,

vehicle = Vehicle.create          # => #<Vehicle id: 1, name: nil, state: "parked">
vehicle.state_event               # => nil
vehicle.state_event = 'invalid'
vehicle.valid?                    # => false
vehicle.errors.full_messages      # => ["State event is invalid"]

vehicle.state_event = 'ignite'
vehicle.valid?                    # => true
vehicle.save                      # => true
vehicle.state                     # => "idling"
vehicle.state_event               # => nil

# Note that this can also be done on a mass-assignment basis:

vehicle = Vehicle.create(:state_event => 'ignite')  # => #<Vehicle id: 1, name: nil, state: "idling">
vehicle.state                                       # => "idling"

This allows you to simply add a state_event field in your resource's edit forms and get state transitions as easily as updating any other attribute.

Now we're obviously still using PUT to trigger events using this method, which isn't RESTful. The gem does, however, provide an interesting example that at least "feels" quite RESTful, despite it using the same non-RESTful method under the covers.

As you can see here and here, the gem's introspection capabilities allow you to present in your forms either the event you would like to fire or the name of that event's resulting state.

<div class="field">
  <%= f.label :state %><br />
  <%= f.collection_select :state_event, @user.state_transitions, :event, :human_to_name, :include_blank => @user.human_state_name %>
</div>

<div class="field">
  <%= f.label :access_state %><br />
  <%= f.collection_select :access_state_event, @user.access_state_transitions, :event, :human_event, :include_blank => "don't change" %>
</div>

Using the latter technique, you get simple form-based updating of the model's state to any valid next state without having to write any extra code. It's not technically RESTful, but it allows you to easily present it that way in the UI.

The cleanliness of this technique combined with the inherent conflicts in trying to cast an event-based state machine into a simple RESTful resource was enough to satisfy me, so hopefully it provides some insight to you as well.

空名 2024-11-07 06:45:35

参加这里的聚会有点晚了,而且离专家还很远,因为我有类似的疑问,但是......

将活动作为资源怎么样?

因此,

PUT /order/53?state_event="pay" #Order.update_attributes({state_event: "pay})

您可以...

POST /order/53/pay     #OrderEvent.create(event_name: :pay)
POST /order/53/cancel  #OrderEvent.create(event_name: :cancel)

在 Order 和 OrderEvent 之间使用 pub/sub 侦听器,或者尝试在 Order 上触发该事件并记录转换消息的回调。它还可以让您方便地审核所有状态更改事件。

创意来自 Shopify 的 Willem Bergen

我错过了什么吗?抱歉,我自己很难理解这一点。

Bit late to the party here and far from an expert as I have a similar query but...

How about making the event a resource?

So instead of...

PUT /order/53?state_event="pay" #Order.update_attributes({state_event: "pay})

You would...

POST /order/53/pay     #OrderEvent.create(event_name: :pay)
POST /order/53/cancel  #OrderEvent.create(event_name: :cancel)

With a pub/sub listener between Order and OrderEvent or callback that attempts to fire that event on Order and records the transition messages. It also gives you a handy audit of all state change events.

Idea stolen from Willem Bergen at Shopify

Am I missing something? Sorry, struggling to understand this myself.

吃素的狼 2024-11-07 06:45:35

如果您的资源具有某种状态属性,您可以使用一种称为 micro-PUT 的技术来更新其状态。

PUT /Customer/1/Status
Content-Type: text/plain

Closed

=> 200 OK
Content-Location: /Customer/1

您可以将资源状态建模为集合并在这些集合之间移动资源。

GET /Customer/1
=>
Content-Type: application/vnd.acme.customer+xml
200 OK


POST /ClosedCustomers
Content-Type: application/vnd.acme.customer+xml
=>
200 OK

POST /OpenCustomers
Content-Type: application/vnd.acme.customer+xml
=>
200 OK

您始终可以使用新的 PATCH 方法

PATCH /Customer/1
Content-Type: application/x-www-form-urlencoded
Status=Closed
=>
200 OK

If your resource has some kind of status attribute, you can use a technique called micro-PUT to update it's status.

PUT /Customer/1/Status
Content-Type: text/plain

Closed

=> 200 OK
Content-Location: /Customer/1

You can model resource states as collections and move resources between those collections.

GET /Customer/1
=>
Content-Type: application/vnd.acme.customer+xml
200 OK


POST /ClosedCustomers
Content-Type: application/vnd.acme.customer+xml
=>
200 OK

POST /OpenCustomers
Content-Type: application/vnd.acme.customer+xml
=>
200 OK

You could always use the new PATCH method

PATCH /Customer/1
Content-Type: application/x-www-form-urlencoded
Status=Closed
=>
200 OK
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文