Entri
Dalam proyek saya bertemu dengan tiga contoh, satu atau lain cara terhubung dengan
teori automata terbatas- Contoh 1. Kode
govnokod yang menghibur . Butuh banyak waktu untuk memahami apa yang terjadi. Fitur karakteristik dari perwujudan teori yang ditunjukkan dalam kode adalah dump yang agak sengit yang kadang-kadang mirip dengan kode prosedural. Fakta bahwa versi kode ini lebih baik untuk tidak menyentuh pada proyek tahu setiap teknologi, metodologi dan spesialis produk. Mereka masuk ke kode ini untuk memperbaiki sesuatu dalam keadaan darurat (ketika itu benar-benar rusak), tidak ada pertanyaan menyelesaikan fitur apa pun. Untuk itu menakutkan untuk istirahat. Fitur mencolok kedua yang mengisolasi tipe ini adalah kehadiran switch yang kuat, layar penuh.
Bahkan ada lelucon tentang skor ini:
Ukuran optimalDi salah satu JPoint, salah satu pembicara, mungkin Nikolai Alimenkov, berbicara tentang berapa banyak kasus dalam saklar yang normal, mengatakan bahwa jawaban teratas adalah "sejauh ini cocok dengan layar." Dengan demikian, jika itu mengganggu dan sakelar Anda sudah tidak normal, ambil dan kurangi ukuran font di IDE
- Contoh 2. Pola Negara . Gagasan utama (bagi mereka yang tidak suka mengikuti tautan) adalah bahwa kami memecah tugas bisnis tertentu menjadi serangkaian status akhir dan menggambarkannya dengan kode.
Kelemahan utama dari Pola Negara adalah bahwa negara tahu tentang satu sama lain, mereka tahu bahwa ada saudara dan memanggil satu sama lain. Kode semacam itu cukup sulit untuk dibuat universal. Misalnya, ketika menerapkan sistem pembayaran dengan beberapa jenis pembayaran, Anda berisiko tergelincir ke dalam Generic-s sehingga pernyataan metode Anda dapat menjadi seperti ini:
private < T extends BaseContextPayment, Q extends BaseDomainPaymentRequest, S, B extends AbstractPaymentDetailBuilder<T, Q, S, B>, F extends AbstractPaymentBuilder<T, Q, S, B> > PaymentContext<T, S> build(final Q request, final Class<F> factoryClass){
Meringkas Negara: suatu implementasi dapat menghasilkan kode yang agak rumit. - Contoh 3 StateMachine Gagasan utama Pola adalah bahwa negara tidak tahu apa-apa tentang satu sama lain, kontrol transisi dilakukan oleh konteks, lebih baik, konektivitas kurang - kode lebih sederhana.
Setelah mengalami semua "kekuatan" dari tipe pertama dan kompleksitas yang kedua, kami memutuskan untuk menggunakan Pattern StateMachine untuk kasus bisnis baru.
Agar tidak menemukan kembali roda, diputuskan untuk mengambil Statemachine Spring sebagai dasar (ini adalah Spring).
Setelah membaca dok, saya pergi ke YouTube dan Habr (untuk memahami bagaimana orang bekerja dengannya, bagaimana rasanya di prod, jenis rake, dll.) Ternyata ada sedikit informasi, di YouTube ada beberapa video, semuanya cukup dangkal. Pada Habrรฉ tentang hal ini saya hanya menemukan satu artikel, serta videonya, cukup dangkal.
Dalam satu artikel, tidak mungkin untuk menggambarkan semua seluk-beluk pekerjaan statemachine Spring, untuk berkeliling di dermaga dan menjelaskan semua kasus, tetapi saya akan mencoba untuk memberitahu yang paling penting dan menuntut, dan tentang menyapu, khususnya kepada saya, ketika saya berkenalan dengan kerangka kerja, informasi yang dijelaskan di bawah ini adalah akan sangat membantu.
Tubuh utama
Kami akan membuat aplikasi Spring Boot, menambahkan starter Web (kami membuat aplikasi web berjalan secepat mungkin). Aplikasi ini akan menjadi abstraksi dari proses pembelian. Produk yang dibeli akan melewati tahap penurunan baru, cadangan, cadangan dan pembelian lengkap.
Sedikit improvisasi, akan ada lebih banyak status dalam proyek nyata, tetapi oh well, kami juga memiliki proyek yang sangat nyata.
Dalam pom.xml aplikasi web yang baru dipanggang, tambahkan ketergantungan pada mesin dan pada tes untuk itu (Web Starter seharusnya sudah, jika dikumpulkan melalui
start.spring.io ):
<dependency> <groupId>org.springframework.statemachine</groupId> <artifactId>spring-statemachine-core</artifactId> <version>2.1.3.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.statemachine</groupId> <artifactId>spring-statemachine-test</artifactId> <version>2.1.3.RELEASE</version> <scope>test</scope> </dependency> <cut />
Buat struktur:

Saya tidak perlu merinci struktur ini, saya akan menjelaskan semuanya secara berurutan, dan akan ada tautan ke sumbernya di akhir artikel.
Jadi ayo pergi.
Kami memiliki proyek bersih dengan dependensi yang diperlukan, untuk permulaan kami akan membuat enum, dengan negara dan acara, abstraksi yang agak sederhana, komponen-komponen ini sendiri tidak membawa logika apa pun.
public enum PurchaseEvent { RESERVE, BUY, RESERVE_DECLINE }
public enum PurchaseState { NEW, RESERVED, CANCEL_RESERVED, PURCHASE_COMPLETE }
Meskipun secara formal, Anda dapat menambahkan bidang ke enum ini, dan meng-hardcode sesuatu di dalamnya yang merupakan karakteristik, misalnya, keadaan tertentu, yang cukup logis (kami melakukan ini dengan menyelesaikan kasus kami, cukup nyaman).
Kami akan mengonfigurasi mesin melalui java config, membuat file config dan, untuk kelas extended EnumStateMachineConfigurerAdapter <PurchaseState, PurchaseEvent>. Karena keadaan dan acara kita adalah enum, antarmuka sesuai, tetapi tidak perlu, semua jenis objek dapat digunakan sebagai generik (kami tidak akan mempertimbangkan contoh lain dalam artikel, karena EnumStateMachineConfigurerAdapter lebih dari cukup menurut pendapat saya).
Poin penting berikutnya adalah apakah satu mesin akan hidup dalam konteks aplikasi: dalam satu instance @EnableStateMachine, atau setiap kali @EnableStateMachineFactory baru akan dibuat. Jika ini adalah aplikasi web multi-pengguna dengan banyak pengguna, maka opsi pertama hampir tidak cocok untuk Anda, jadi kami akan menggunakan yang kedua sebagai yang lebih populer. StateMachine juga dapat dibuat melalui pembangun sebagai kacang biasa (lihat dokumentasi), yang nyaman dalam beberapa kasus (misalnya, Anda membutuhkan mesin untuk secara eksplisit dinyatakan sebagai kacang), dan jika itu adalah kacang yang terpisah, maka kami dapat menentukan ruang lingkup kami mis. sesi atau permintaan. Dalam proyek kami, pembungkus (fitur dari logika bisnis kami) diimplementasikan di atas kacang statemachine, pembungkus adalah singleton, dan mesin prototipe itu sendiri
MenyapuBagaimana cara mengimplementasikan prototipe di Singapura?
Bahkan, yang perlu Anda lakukan adalah mendapatkan kacang baru dari applicationContext setiap kali Anda mengakses objek. Adalah dosa untuk menyuntikkan applicationContext ke dalam logika bisnis, oleh karena itu, kacang statemachine harus mengimplementasikan antarmuka dengan setidaknya satu metode, atau metode abstrak (metode injeksi), ketika membuat konfigurasi java, Anda perlu menerapkan metode abstrak yang ditunjukkan, dan dalam implementasi kami akan menarik dari applicationContext kacang baru. Ini adalah praktik normal untuk memiliki tautan ke applicationContext di kelas config, dan melalui metode abstrak kita akan memanggil .getBean ();
Kelas EnumStateMachineConfigurerAdapter memiliki beberapa metode, selain itu kita mengkonfigurasi mesin.
Untuk memulai, daftarkan status:
@Override public void configure(final StateMachineStateConfigurer<PurchaseState, PurchaseEvent> states) throws Exception { states .withStates() .initial(NEW) .end(PURCHASE_COMPLETE) .states(EnumSet.allOf(PurchaseState.class)); }
initial (NEW) adalah status mesin akan setelah bean dibuat, end (PURCHASE_COMPLETE) adalah status dengan pergi ke mana mesin akan mengeksekusi statemachine.stop (), untuk mesin non-deterministik (sebagian besar) tidak relevan, tetapi ada sesuatu yang perlu ditentukan . .states (EnumSet.allOf (PurchaseState.class) daftar semua status, Anda dapat mendorong secara massal.
Konfigurasikan pengaturan mesin global
@Override public void configure(final StateMachineConfigurationConfigurer<PurchaseState, PurchaseEvent> config) throws Exception { config .withConfiguration() .autoStartup(true) .listener(new PurchaseStateMachineApplicationListener()); }
Di sini autoStartup menentukan apakah mesin akan dimulai segera setelah pembuatan secara default, dengan kata lain - apakah itu akan secara otomatis beralih ke status BARU (salah secara default). Segera, kami mendaftarkan pendengar untuk konteks mesin (tentangnya beberapa saat kemudian), dalam konfigurasi yang sama Anda dapat mengatur TaskExecutor terpisah, yang nyaman ketika Aksi panjang dilakukan pada beberapa transisi mereka, dan aplikasi harus melangkah lebih jauh.
Transisi itu sendiri:
@Override public void configure(final StateMachineTransitionConfigurer<PurchaseState, PurchaseEvent> transitions) throws Exception { transitions .withExternal() .source(NEW) .target(RESERVED) .event(RESERVE) .action(reservedAction(), errorAction()) .and() .withExternal() .source(RESERVED) .target(CANCEL_RESERVED) .event(RESERVE_DECLINE) .action(cancelAction(), errorAction()) .and() .withExternal() .source(RESERVED) .target(PURCHASE_COMPLETE) .event(BUY) .guard(hideGuard()) .action(buyAction(), errorAction()); }
Semua logika transisi atau transisi diatur di sini, Guard dapat digantung pada transisi, komponen yang selalu mengembalikan boolean, apa sebenarnya yang akan Anda periksa pada transisi dari satu status ke status lain sesuai kebijaksanaan Anda, logika apa pun bisa sempurna di Guard, ini adalah komponen yang sepenuhnya biasa tapi dia harus mengembalikan boolean. Dalam kerangka kerja proyek kami, misalnya, HideGuard dapat memeriksa pengaturan tertentu yang dapat ditetapkan pengguna (tidak menunjukkan produk ini) dan, sesuai dengan itu, jangan biarkan mesin masuk ke keadaan dilindungi oleh Guard. Saya perhatikan bahwa Guard, hanya satu yang dapat ditambahkan ke satu transisi dalam konfigurasi, desain seperti itu tidak akan berfungsi:
.withExternal() .source(RESERVED) .target(PURCHASE_COMPLETE) .event(BUY) .guard(hideGuard()) .guard(veryHideGuard())
Lebih tepatnya itu akan berhasil, tetapi hanya penjaga pertama (hideGuard ())
Tetapi Anda dapat menambahkan beberapa Tindakan (sekarang kita berbicara tentang Tindakan, yang kami tentukan dalam konfigurasi transisi), saya pribadi mencoba menambahkan tiga Tindakan ke satu transisi.
.withExternal() .source(NEW) .target(RESERVED) .event(RESERVE) .action(reservedAction(), errorAction())
argumen kedua adalah ErrorAction, kontrol akan sampai ke sana jika ReservedAction melempar pengecualian (lempar ke).
MenyapuIngatlah bahwa jika dalam Aksi Anda Anda masih menangani kesalahan melalui try / catch, maka Anda tidak akan masuk ke ErrorAction, jika Anda perlu memproses dan masuk ke ErrorAction maka Anda harus melempar RuntimeException () dari tangkapan, misalnya (Anda sendiri mengatakan bahwa itu sangat perlu).
Selain "menggantung" Aksi dalam transisi, Anda juga bisa "menggantung" mereka dalam metode konfigurasi untuk keadaan, dalam kira-kira bentuk berikut:
@Override public void configure(final StateMachineStateConfigurer<PurchaseState, PurchaseEvent> states) throws Exception { states .withStates() .initial(NEW) .end(PURCHASE_COMPLETE) .stateEntry() .stateExit() .state() .states(EnumSet.allOf(PurchaseState.class)); }
Itu semua tergantung pada bagaimana Anda ingin menjalankan aksinya.
MenyapuPerhatikan bahwa jika Anda menentukan tindakan saat mengonfigurasi status (), seperti itu
states .withStates() .initial(NEW) .end(PURCHASE_COMPLETE) .state(randomAction())
itu akan dieksekusi secara tidak sinkron, diasumsikan bahwa jika Anda mengatakan .stateEntry (), misalnya, Action harus dieksekusi langsung di pintu masuk, tetapi jika Anda mengatakan .state () maka Action harus dieksekusi dalam status target, tetapi itu tidak begitu penting ketika.
Dalam proyek kami, kami mengonfigurasi semua Tindakan pada konfigurasi transisi, karena Anda dapat menggantinya beberapa sekaligus.
Versi final dari konfigurasi akan terlihat seperti ini:
@Configuration @EnableStateMachineFactory public class StateMachineConfig extends EnumStateMachineConfigurerAdapter<PurchaseState, PurchaseEvent> { @Override public void configure(final StateMachineConfigurationConfigurer<PurchaseState, PurchaseEvent> config) throws Exception { config .withConfiguration() .autoStartup(true) .listener(new PurchaseStateMachineApplicationListener()); } @Override public void configure(final StateMachineStateConfigurer<PurchaseState, PurchaseEvent> states) throws Exception { states .withStates() .initial(NEW) .end(PURCHASE_COMPLETE) .stateEntry() .stateExit() .state() .states(EnumSet.allOf(PurchaseState.class)); } @Override public void configure(final StateMachineTransitionConfigurer<PurchaseState, PurchaseEvent> transitions) throws Exception { transitions .withExternal() .source(NEW) .target(RESERVED) .event(RESERVE) .action(reservedAction(), errorAction()) .and() .withExternal() .source(RESERVED) .target(CANCEL_RESERVED) .event(RESERVE_DECLINE) .action(cancelAction(), errorAction()) .and() .withExternal() .source(RESERVED) .target(PURCHASE_COMPLETE) .event(BUY) .guard(hideGuard()) .action(buyAction(), errorAction()); } @Bean public Action<PurchaseState, PurchaseEvent> reservedAction() { return new ReservedAction(); } @Bean public Action<PurchaseState, PurchaseEvent> cancelAction() { return new CancelAction(); } @Bean public Action<PurchaseState, PurchaseEvent> buyAction() { return new BuyAction(); } @Bean public Action<PurchaseState, PurchaseEvent> errorAction() { return new ErrorAction(); } @Bean public Guard<PurchaseState, PurchaseEvent> hideGuard() { return new HideGuard(); } @Bean public StateMachinePersister<PurchaseState, PurchaseEvent, String> persister() { return new DefaultStateMachinePersister<>(new PurchaseStateMachinePersister()); }
Perhatikan skema mesin, sangat jelas terlihat pada apa yang kami kodekan (transisi mana yang valid, mana Guard yang melindungi status dan apa yang akan dilakukan ketika status diaktifkan, yang Action).

Mari kita buat pengontrolnya:
@RestController @SuppressWarnings("unused") public class PurchaseController { private final PurchaseService purchaseService; public PurchaseController(PurchaseService purchaseService) { this.purchaseService = purchaseService; } @RequestMapping(path = "/reserve") public boolean reserve(final String userId, final String productId) { return purchaseService.reserved(userId, productId); } @RequestMapping(path = "/cancel") public boolean cancelReserve(final String userId) { return purchaseService.cancelReserve(userId); } @RequestMapping(path = "/buy") public boolean buyReserve(final String userId) { return purchaseService.buy(userId); } }
antarmuka layanan
public interface PurchaseService { boolean reserved(String userId, String productId); boolean cancelReserve(String userId); boolean buy(String userId); }
MenyapuTahukah Anda mengapa penting untuk membuat kacang melalui antarmuka saat bekerja dengan Spring? Menghadapi masalah ini (yah, ya, ya, dan Zhenya Borisov berbicara di ripper), ketika sekali di controller mereka mencoba mengimplementasikan antarmuka yang tidak kosong dan berimprovisasi. Spring membuat proksi untuk komponen, dan jika komponen tidak mengimplementasikan antarmuka apa pun, maka ia akan melakukannya melalui CGLIB, tetapi segera setelah Anda mengimplementasikan beberapa antarmuka - Spring akan mencoba membuat proxy melalui proxy dinamis, sebagai hasilnya Anda akan mendapatkan jenis objek yang tidak dapat dipahami dan NoSuchBeanDefinitionException .
Poin penting berikutnya adalah bagaimana Anda akan memulihkan keadaan mesin Anda, karena untuk setiap panggilan akan dibuat kacang baru yang tidak tahu apa-apa tentang status mesin Anda sebelumnya dan konteksnya.
Untuk tujuan ini, pegas statemachine memiliki mekanisme Persistens:
public class PurchaseStateMachinePersister implements StateMachinePersist<PurchaseState, PurchaseEvent, String> { private final HashMap<String, StateMachineContext<PurchaseState, PurchaseEvent>> contexts = new HashMap<>(); @Override public void write(final StateMachineContext<PurchaseState, PurchaseEvent> context, String contextObj) { contexts.put(contextObj, context); } @Override public StateMachineContext<PurchaseState, PurchaseEvent> read(final String contextObj) { return contexts.get(contextObj); } }
Untuk implementasi naif kami, kami menggunakan Peta biasa sebagai toko negara, dalam implementasi non-naif itu akan menjadi semacam database, perhatikan String tipe generik ketiga, ini adalah kunci yang digunakan untuk menyimpan keadaan mesin Anda, dengan semua status, variabel dalam konteks, id dan sebagainya. Dalam contoh saya, saya menggunakan id pengguna untuk tombol simpan, yang dapat berupa kunci apa saja (user session_id, login unik, dll.).
MenyapuDalam proyek kami, mekanisme untuk menyimpan dan memulihkan keadaan dari kotak tidak cocok untuk kami, karena kami menyimpan status mesin dalam database dan dapat diubah oleh pekerjaan yang tidak tahu apa-apa tentang mesin.
Saya harus mempercepat pada status yang diterima dari database, melakukan beberapa InitAction yang, ketika mesin mulai, menerima status dari database, dan mengaturnya secara paksa, dan hanya kemudian melemparkan peristiwa, contoh kode yang melakukan hal di atas:
stateMachine .getStateMachineAccessor() .doWithAllRegions(access -> { access.resetStateMachine(new DefaultStateMachineContext<>({ResetState}, null, null, null, null)); }); stateMachine.start(); stateMachine.sendEvent({NewEventFromResetState});
Kami akan mempertimbangkan implementasi layanan di setiap metode:
@Override public boolean reserved(final String userId, final String productId) { final StateMachine<PurchaseState, PurchaseEvent> stateMachine = stateMachineFactory.getStateMachine(); stateMachine.getExtendedState().getVariables().put("PRODUCT_ID", productId); stateMachine.sendEvent(RESERVE); try { persister.persist(stateMachine, userId); } catch (final Exception e) { e.printStackTrace(); return false; } return true; }
Kami mendapatkan mobil dari pabrik, meletakkan parameter dalam konteks mesin, dalam kasus kami ini adalah productId, konteksnya adalah semacam kotak di mana Anda dapat meletakkan semua yang Anda butuhkan, di mana pun ada akses ke kacang statemachine atau konteksnya, karena mesin mulai secara otomatis ketika konteks dimulai , kemudian setelah start, mobil kami akan dalam status BARU, membuang acara pada pemesanan barang.
Dua metode lainnya serupa:
@Override public boolean cancelReserve(final String userId) { final StateMachine<PurchaseState, PurchaseEvent> stateMachine = stateMachineFactory.getStateMachine(); try { persister.restore(stateMachine, userId); stateMachine.sendEvent(RESERVE_DECLINE); } catch (Exception e) { e.printStackTrace(); return false; } return true; } @Override public boolean buy(final String userId) { final StateMachine<PurchaseState, PurchaseEvent> stateMachine = stateMachineFactory.getStateMachine(); try { persister.restore(stateMachine, userId); stateMachine.sendEvent(BUY); } catch (Exception e) { e.printStackTrace(); return false; } return true; }
Di sini pertama-tama kita mengembalikan keadaan mesin untuk userId dari pengguna tertentu, dan kemudian melempar peristiwa yang sesuai dengan metode api.
Perhatikan bahwa productId tidak muncul dalam metode lagi, kami menambahkannya ke konteks mesin dan akan mendapatkannya setelah mengembalikan mesin dari cadangannya.
Dalam implementasi Tindakan, kita akan mendapatkan id produk dari konteks mesin dan menampilkan pesan yang sesuai dengan transisi dalam log, misalnya, saya akan memberikan kode ReservedAction:
public class ReservedAction implements Action<PurchaseState, PurchaseEvent> { @Override public void execute(StateContext<PurchaseState, PurchaseEvent> context) { final String productId = context.getExtendedState().get("PRODUCT_ID", String.class); System.out.println(" " + productId + " ."); } }
Kami tidak bisa tidak menyebutkan pendengar, yang menawarkan beberapa skrip yang bisa Anda gunakan, lihat sendiri:
public class PurchaseStateMachineApplicationListener implements StateMachineListener<PurchaseState, PurchaseEvent> { @Override public void stateChanged(State<PurchaseState, PurchaseEvent> from, State<PurchaseState, PurchaseEvent> to) { if (from.getId() != null) { System.out.println(" " + from.getId() + " " + to.getId()); } } @Override public void stateEntered(State<PurchaseState, PurchaseEvent> state) { } @Override public void stateExited(State<PurchaseState, PurchaseEvent> state) { } @Override public void eventNotAccepted(Message<PurchaseEvent> event) { System.out.println(" " + event); } @Override public void transition(Transition<PurchaseState, PurchaseEvent> transition) { } @Override public void transitionStarted(Transition<PurchaseState, PurchaseEvent> transition) { } @Override public void transitionEnded(Transition<PurchaseState, PurchaseEvent> transition) { } @Override public void stateMachineStarted(StateMachine<PurchaseState, PurchaseEvent> stateMachine) { System.out.println("Machine started"); } @Override public void stateMachineStopped(StateMachine<PurchaseState, PurchaseEvent> stateMachine) { } @Override public void stateMachineError(StateMachine<PurchaseState, PurchaseEvent> stateMachine, Exception exception) { } @Override public void extendedStateChanged(Object key, Object value) { } @Override public void stateContext(StateContext<PurchaseState, PurchaseEvent> stateContext) { } }
Satu-satunya masalah adalah bahwa ini adalah antarmuka, yang berarti bahwa Anda perlu menerapkan semua metode ini, tetapi karena Anda tidak mungkin membutuhkan semuanya, beberapa dari mereka akan hang kosong, yang cakupannya akan mengatakan bahwa metode tidak tercakup oleh tes.
Di sini, di lisener kita dapat benar-benar menggantung metrik pada peristiwa mesin yang sama sekali berbeda (misalnya, pembayaran tidak melalui, mesin sering masuk ke semacam status PAYMENT_FAIL, kita mendengarkan transisi, dan jika mesin masuk ke status yang salah - kita menulis, dalam log aneh, atau pangkalan atau hubungi polisi, apa pun).
MenyapuAda acara stateMachineError di lisener-e, tetapi dengan nuansa, ketika Anda memiliki pengecualian dan Anda menanganinya dalam tangkapan, mesin tidak menganggap bahwa ada kesalahan, Anda perlu berbicara secara eksplisit dalam tangkapan
stateMachine.setStateMachineError (pengecualian) dan menyampaikan kesalahan.
Sebagai pemeriksaan atas apa yang telah kami lakukan, kami akan mengeksekusi dua kasus:
- 1. Pemesanan dan penolakan pembelian selanjutnya. Kami akan mengirimkan aplikasi permintaan untuk URI "/ cadangan", dengan parameter userId = 007, productId = 10001, dan setelah itu permintaan "/ batal" dengan parameter userId = 007 output konsol akan sebagai berikut:
Machine started
10001 .
NEW RESERVED
Machine started
10001
RESERVED CANCEL_RESERVED
- 2. Pemesanan dan pembelian berhasil:
Machine started
10001 .
NEW RESERVED
Machine started
10001
RESERVED PURCHASE_COMPLETE
Kesimpulan
Sebagai kesimpulan, saya akan memberikan contoh pengujian kerangka kerja, saya pikir semuanya akan menjadi jelas dari kode, Anda hanya perlu ketergantungan pada mesin uji, dan Anda dapat memeriksa konfigurasi secara deklaratif.
@Test public void testWhenReservedCancel() throws Exception { StateMachine<PurchaseState, PurchaseEvent> machine = factory.getStateMachine(); StateMachineTestPlan<PurchaseState, PurchaseEvent> plan = StateMachineTestPlanBuilder.<PurchaseState, PurchaseEvent>builder() .defaultAwaitTime(2) .stateMachine(machine) .step() .expectStates(NEW) .expectStateChanged(0) .and() .step() .sendEvent(RESERVE) .expectState(RESERVED) .expectStateChanged(1) .and() .step() .sendEvent(RESERVE_DECLINE) .expectState(CANCEL_RESERVED) .expectStateChanged(1) .and() .build(); plan.test(); } @Test public void testWhenPurchaseComplete() throws Exception { StateMachine<PurchaseState, PurchaseEvent> machine = factory.getStateMachine(); StateMachineTestPlan<PurchaseState, PurchaseEvent> plan = StateMachineTestPlanBuilder.<PurchaseState, PurchaseEvent>builder() .defaultAwaitTime(2) .stateMachine(machine) .step() .expectStates(NEW) .expectStateChanged(0) .and() .step() .sendEvent(RESERVE) .expectState(RESERVED) .expectStateChanged(1) .and() .step() .sendEvent(BUY) .expectState(PURCHASE_COMPLETE) .expectStateChanged(1) .and() .build(); plan.test(); }
MenyapuJika Anda tiba-tiba ingin menguji mesin Anda tanpa meningkatkan konteks dengan tes unit biasa, Anda dapat membuat mesin melalui pembangun (sebagian dibahas di atas), membuat instance kelas dengan konfigurasi dan mendapatkan tindakan dan penjaga dari sana, itu akan bekerja tanpa konteks, Anda dapat menulis sedikit tes Kerangka kerja ini pada mock, ini merupakan nilai tambah, Anda dapat memeriksa Tindakan mana yang dipanggil, yang tidak, berapa kali, dll, pada kasus yang berbeda.
PS
Mobil kami bekerja secara produktif, sejauh ini kami belum mengalami masalah operasional, fitur akan datang di mana kami dapat menggunakan sebagian besar komponen mesin saat ini ketika menerapkan yang baru (Penjaga dan beberapa Tindakan sempurna)Catatan
Saya tidak mempertimbangkannya dalam artikel, tetapi saya ingin menyebutkan peluang seperti pilihan, ini adalah semacam pemicu yang bekerja pada prinsip sakelar, di mana Penjaga digantung pada kasing, dan mesin bergantian mencoba untuk pergi ke keadaan itu, yang dijelaskan dalam konfigurasi pilihan dan di mana Penjaga akan membiarkannya pergi, tanpa beberapa Acara, akan lebih mudah ketika, ketika menginisialisasi mesin, kita perlu secara otomatis beralih ke beberapa jenis pseudo-host.Referensi
Sumber Doca