상업 상태기

TL;DR은 본고에서 상태기와 업무 프로세스 모델링에 어떻게 응용되는지, 그리고 Elixir와 Exto를 실현하는 특정한 라이브러리를 소개할 것이다.
자판기를 생각해 봅시다.해당 작업은 구속조건 세트로 정의됩니다.지폐가 없을 때 콜라 한 잔 살 수 있습니까?아니오. 안에 1달러를 넣으면 음료수 가격이 2달러인데 바뀌나요?아직 없어요,duh.이 모든 속성의 조합은 하나의 상태를 구성한다.하나의 상태를 이 특정 자동판매기의 축소판 중 시공 연속체의 일부분으로 상상하다.

이런 상황에서 그 주는 현재 잔액만 고려했지만 실제로는 더 많은 변수가 있을 것이다.콜라와 과자의 공급은 제한을 받지 않습니까?그럴 리가 없어요.우리는 반드시 한 나라를 분배하는 과정을 고려해야 합니까?이 가능하다, ~할 수 있다,...하지만 이제 돈에 중점을 두자. 동전을 받아들이기 시작하면 더욱 복잡해진다.무슨 일이 일어날지 봅시다. 우리는 단지 4분의 1만 추가하면 됩니다.

보이다순수한 유한 상태 자동기로 실제 업무 절차를 모델링하는 것은 매우 복잡할 것이다.반대로 우리는 상태의 개념을 하나의 라벨, 하나의 상태의 일반적인 이름으로 간소화할 수 있다.우리는 지폐를 수집하고 있습니까, 아니면 동전을 수집하고 있습니까? 우리는 collecting이라고 합니다.저희가 상품을 나눠주고 있나요?그래, 이게 어떻게 된 일인지 봐라.우리는 여전히 임의로 복잡할 수 있는 내부 변수를 보존할 것이지만, 지금은 기계의 조작 모델을 이러한 변수의 모든 가능한 조합이 아니라 하나의 상태로 간주하기를 원한다.
이것은 우리로 하여금 이 상태기를 분배를 포함하여 단지 두 가지 상태로 줄일 수 있게 한다.도표를 요약하고 언어를 소개합니다.

원은 States입니다. 당신은 이미 알아맞혔을 것입니다.화살표와 연도에서 발생하는 모든 것은 Transitions이다.직사각형은 Callbacks 또는 생명주기의 특정 시간에 내부 상태 변수를 돌연변이시키는 동작이다.마름모꼴은 Guard으로 이 조건을 만족해야만 계속 이행할 수 있다.마지막으로, 이벤트 - 외부 신호, 트리거 변환의 선택적 값을 포함합니다.이 그림은 약간 지루하지만 DSL이나 순수한 영어로 작성할 때 매우 간단하다.
  • collectingput X에 있을 때 균형을 맞추기 위해 X를 추가한 다음 collecting으로 이동합니다
  • collectingget ITEM에 있을 때 잔액>= 항목.가격, 그리고 dispensing으로 이동
  • dispensingdone에서 항목을 뺀다.가격은 잔액에서 collecting으로 이동
  • 지금까지 우리는 다음과 같은 내용을 정의했다.
  • 두 주 collectingdispensing
  • 세 사건 put X, get ITEM, done
  • get ITEM 사건의 수비수: balance >= ITEM.price
  • 두 차례 조정: balance + Xput Xbalance - ITEM.pricedone
  • 내부 상태는 2개의 변수로 구성된다. balanceitem;후자는 분배로 이동하고 지우도록 설정합니다.실제로 이것도 리셋이지만, 그것들은 위의 그림에서 부족하다.응, 모델링은 쉽지 않아. 우리는 보통 그 다음에 이 모델을 여러 번 다시 방문한다.

    불로장생약 중의 상태기


    state_machine 으로 Elixir에서 같은 상태기를 모델링하는 것은 매우 간단하다.저는 상품화를 완성하기 위해 추가 이벤트를 추가했습니다. 지금은 리셋과 보호의 실현을 생략했습니다.
    defmodule VendingMachine do
      alias VendingMachine, as: VM
      use StateMachine
    
      defstruct state: :collecting,
                balance: 0,
                merch: %{},
                dispensing: nil
    
      defmachine field: :state do
        state :collecting
        state :dispensing
    
        event :deposit, after: &VM.deposit/2 do
          transition from: :collecting, to: :collecting
        end
    
        event :buy, if: &VM.can_sell?/2, after: &VM.reserve/2 do
          transition from: :collecting, to: :dispensing
        end
    
        event :done, after: &VM.charge/1 do
          transition from: :dispensing, to: :collecting
        end
    
        event :fulfill, after: &VM.fulfill/2 do
          transition from: :collecting, to: :collecting
        end
      end
    end
    
    전환 과정에서 잘못된 상태 이름을 입력하면 컴파일할 때 포획된다는 것이 멋진 특징이다.정의가 검증되고 있습니다.우리가 두 개의 상태만 있을 때, 그것은 쓸모없어 보일 수도 있지만, 더 큰 상태기에서, 그것은 생명을 구할 수 있다.
    이제 리셋을 봅시다.
    def deposit(%{balance: balance} = model, %{payload: x})
      when is_integer(x) and x > 0
    do
      {:ok, %{model | balance: balance + x}}
    end
    
    def deposit(_, _) do
      {:error, "Expecting some positive amount of money to be deposited"}
    end
    
    리셋은arity 0, 1, 2일 수 있습니다.영산술 리셋은 상태와 무관한 부작용만 일으킬 뿐이다.리셋된 첫 번째 매개 변수는 모델 자체입니다.두 번째는 현재 변환을 지원하는 메타데이터를 포함하는 상하문입니다.이것은 이벤트 부하, 변환에 대한 정보(예를 들어 신구 상태), 그리고 상태기에 정의된 링크를 포함한다.
    실행할 때 구조적으로 각 리셋의 반환 값을 분석하여 적당한 조작을 확정한다.{:error, error}으로 되돌아갈 수 있습니다. 이것은 전환을 중단하고 최종적으로 {:error, {callsite, error}}으로 되돌아갈 것입니다.이곳의callsite는 거의 리셋을 촉발하는 시간(예를 들어 after_event)이다.{:ok, updated_context}을 반환하면 현재 컨텍스트가 업데이트됩니다.이것은 하드코어이지만, 너는 할 수 있다.일반적으로 모델을 업데이트하고 싶을 수도 있습니다.이를 위해 {:ok, updated_model}으로 돌아가면 상하문에서 대체됩니다.
    중요한 경고는 현재 리셋에서 완전히 제한된 함수 포획 (&Module.fun/arity) 만 지원합니다.미래의 버전에서는 lambda와 가능한 국부 함수와 원자를 지원할 것입니다.
    다음은 보호 장치 can_sell?:
    def can_sell?(model, %{payload: item}) do
      model.merch[item]
      && model.merch[item][:available] > 0
      && model.merch[item][:price] <= model.balance
    end
    
    우선 우리는 item을 하나의 종류로 재고에 존재하는 것을 확보하고, 그 종류가 현재 사용 가능한지 확인한 다음, 마지막에 우리는 잔액이 충분하다는 것을 확보한다.한 가지 주의해야 할 것이 있다.state_기계의 메커니즘으로 인해 수위는 아무런 흔적도 남기지 않는다.그것들은 가능한 경로가 많기 때문에 일치하는 변환을 찾으려고 시도할 때 순서대로 실행됩니다.다시 말하면 사용자에게 이벤트는 발생할 수 없을 것 같습니다. 내성 도구를 사용하면 allowed_events의 특정 모델을 검사하여 미리 발견할 수 있습니다.
    나머지 리셋은 보기에 매우 간단할 것 같다.
    def reserve(model, %{payload: item}) do
      {:ok, %{model | dispensing: item}}
    end
    
    def charge(%{balance: balance, dispensing: item, merch: merch} = model) do
      {:ok, %{model |
        balance: balance - merch[item][:price],
        merch: put_in(merch[item][:available], merch[item][:available] - 1),
        dispensing: nil
      }}
    end
    
    def fulfill(%{merch: merch} = model, %{payload: additions})
      when is_map(additions)
    do
      {:ok, %{model |
        merch: Map.merge(merch, additions, fn _, existing, new ->
          %{new | available: new.available + existing.available}
        end)
      }}
    end
    
    
    놀 때가 됐어.나는 이 예시 프로젝트를 위해 작은 repo을 만들어서 모든 사람이 복제하고 현지에서 시도할 수 있도록 했다.그러나 나는 여기서 테스트 서열을 발표할 것이다. 그것은 말하지 않아도 알 수 있다.
    alias VendingMachine, as: VM
    vm = %VM{}
    
    # Let's try to load it with some coke and cookies
    assert {:ok, vm} = VM.trigger(vm, :fulfill, %{
      coke: %{price: 2, available: 1},
      cookie: %{price: 1, available: 5}
    })
    
    # And one more coke to ensure correct merging
    assert {:ok, vm} = VM.trigger(vm, :fulfill, %{
      coke: %{price: 2, available: 1},
    })
    
    assert vm.merch.coke.price == 2
    assert vm.merch.coke.available == 2
    assert vm.merch.cookie.price == 1
    assert vm.merch.cookie.available == 5
    
    # Now let's grab a coke
    assert {:error, {:transition, _}} = VM.trigger(vm, :buy, :coke)
    
    # But wait, we could actually tell that before even trying:
    refute :buy in VM.allowed_events(vm)
    
    # Oh right, no money in there yet
    assert {:ok, vm} = VM.trigger(vm, :deposit, 1)
    assert {:ok, vm} = VM.trigger(vm, :deposit, 1)
    assert vm.balance == 2
    
    # Huh, hacking much? Note how error carries the callsite where it occurred
    assert {:error, {:after_event, error}} = VM.trigger(vm, :deposit, -10)
    assert error == "Expecting some positive amount of money to be deposited"
    
    # Gimme my coke already
    assert {:ok, vm} = VM.trigger(vm, :buy, :coke)
    assert vm.state == :dispensing
    
    # While it's busy, can I maybe ask for a cookie, since the balance is still there?
    assert {:error, {:transition, "Couldn't resolve transition"}} = VM.trigger(vm, :buy, :cookie)
    
    # Okay, the can is rolling into the tray, crosses the optical sensor, and it reports to VM...
    assert {:ok, vm} = VM.trigger(vm, :done)
    assert vm.state == :collecting
    assert vm.balance == 0
    assert vm.merch.coke.available == 1
    
    우리가 자동판매기 모듈에서 defmachine매크로를 사용할 때, 그것은 약간의 보조 기능을 만들 것이다.가장 중요한 것은 trigger(model, event, payload)이다.호출할 때, 이벤트를 실행하려고 시도합니다.변환이 성공하면 이 함수는 {:ok, updated\u모델}, 변환이 성공하지 못하면 {:error, {callsite, error}로 돌아갑니다. callsite를 검사하면 거부된 위치를 찾을 수 있습니다.
    StateMachine은 트랜잭션에 트리거를 포장하고 Repo를 호출하여 상자를 열면 사용할 수 있는 Ecto를 지원합니다.상태 필드의 업데이트 () 는 설정이 필요합니다.GenStatem 정의를 자동으로 생성하는 프로세스이기도 합니다.다음 게시물에는 더 많은 소개가 있을 것이다.
    링크:
  • package
  • repo
  • docs
  • 좋은 웹페이지 즐겨찾기