Transaction Script Service
On this page
- Sekilas
- Gagasannya secara singkat
- A. Satu prosedur per request
- B. Logika dan alur tinggal bersama
- C. Cukup selama logikanya sederhana
- Kenapa saya memilihnya
- Kesepakatan saya
- Cara saya memeriksa module
- Di mana saya tidak memakainya
- Ketika aturan tumbuh melampaui satu prosedur
- Ketika service ditulis dalam Rust
- Rujukan
- Istilah
Transaction Script Service adalah bentuk yang saya berikan pada web service Python kecil yang punya satu tugas: tiap endpoint adalah satu prosedur yang membawa request dari awal sampai selesai, dan semua yang dibutuhkan prosedur itu — mengambil, mengolah, memverifikasi, konfigurasi, telemetry — tinggal di sampingnya dalam module yang dinamai menurut kerjanya. Pola di bawahnya adalah Transaction Script milik Martin Fowler, dari buku Patterns of Enterprise Application Architecture; kesepakatan di sekelilingnya milik saya sendiri.
Inilah salah satu arsitektur yang saya pakai, dan saya memilihnya dengan sengaja untuk service yang seluruh tujuannya satu pipeline. Begitu aturan di dalam pipeline itu mulai tumbuh, atau saya perlu menguji aturan itu tanpa dunia luar, service itu saya pindahkan ke Clean Architecture.
Sekilas
| Asal | Pola Transaction Script dari Martin Fowler, dengan kesepakatan saya sendiri untuk service Python |
|---|---|
| Saya pakai untuk | Service Python kecil dengan satu tugas yang harus cepat berfungsi |
| Tak saya pakai untuk | Service yang aturan bisnisnya terus tumbuh, atau yang butuh test tanpa infrastrukturnya |
| Bahasa | Python, dengan FastAPI atau Flask |
Gagasannya secara singkat
A. Satu prosedur per request
Fowler menggambarkan transaction script sebagai prosedur yang menerima masukan satu request, mengerjakan semuanya, lalu mengembalikan hasilnya. Tiap langkah adalah satu baris di prosedur itu, berurutan seperti kejadiannya: periksa pemanggil, baca masukan, ambil yang dibutuhkan, kerjakan, simpan hasilnya, jawab.
B. Logika dan alur tinggal bersama
Tak ada model domain terpisah tempat prosedur mendelegasikan kerjanya. Keputusan diambil di titik yang dilewati alurnya. Satu request jadi mudah diikuti, tapi aturan yang dipakai banyak request jadi lebih sulit dijaga di satu tempat.
C. Cukup selama logikanya sederhana
Fowler menyebut pola ini pilihan wajar ketika logikanya kecil, dan mengingatkan bahwa ia berubah menjadi prosedur yang berulang dan kusut ketika logikanya tumbuh. Nasihat yang sama menentukan batas tempat saya memakainya.
Kenapa saya memilihnya
Seluruh request muat di satu layar. Ketika ada yang gagal di production, saya ingin membuka satu fungsi dan membaca, dari atas ke bawah, semua yang dikerjakan service terhadap request itu. Transaction script memberikan persis itu: tanpa melompat melewati interface, factory, atau lapisan sebelum sampai ke baris yang penting.
Inilah jalan tercepat menuju service yang berfungsi. Service yang dibangun di sekitar satu pipeline — ambil file lalu olah, ambil stream lalu nilai — butuh pipeline-nya lebih dulu. Menulisnya sebagai satu prosedur membuat versi pertama bisa menjawab request sungguhan begitu pipeline-nya bekerja, dan strukturnya tak menghalangi library yang mengerjakan bagian beratnya.
Ia dibundel menjadi satu binary tanpa kejutan. Service seperti ini saya kirim sebagai satu executable yang dibangun dengan PyInstaller. Kumpulan module datar yang di-import langsung mudah ditemukan bundler, dan sedikit import dinamis yang terlewat dicantumkan eksplisit di satu tempat.
Ia jujur tentang apa service itu. Service dengan satu tugas tak butuh model domain yang tak akan pernah dipakainya. Menambahkan lapisan untuk pipeline lima langkah hanya membuat kode tampak lebih terawat daripada yang dibutuhkan, sementara setiap perubahan jadi lebih panjang.
Kesepakatan saya
Service saya yang mengikuti arsitektur ini punya salah satu dari dua pohon. Yang pertama satu paket datar; yang kedua memecah module yang sama ke tiga folder bila router-nya lebih dari satu:
Satu endpoint terbaca sebagai daftar langkah yang dikerjakannya, dan tiap langkah langsung memanggil module urusannya:
=
=
=
= await
= await
await
return | Konsep | Tempatnya |
|---|---|
| Prosedur untuk satu request | satu fungsi endpoint di api.py atau di router |
| Kerja milik satu urusan | module yang dinamai menurut urusannya, seperti fetchers.py atau verifier.py |
| Konfigurasi | satu objek, dimuat sekali dan di-import di mana pun dibutuhkan |
| Client berumur panjang: sesi HTTP, cache | instance tingkat module, dibuat sekali |
| Telemetry | module tersendiri, diinisialisasi sebelum app dan router-nya dibangun |
| Titik masuk | server.py, yang hanya membangun app dan menjalankan server |
Beberapa aturan menjaga prosedurnya tetap terbaca. Endpoint boleh panjang, tapi terbaca menurut urutan kejadian dan tak menyembunyikan langkah di helper yang hanya dipakainya sendiri. Module urusan tak pernah meng-import lapisan endpoint dan tak saling meng-import secara melingkar. Instance tingkat module hanya untuk konfigurasi dan client, tak pernah untuk data request. Kerja yang memblokir, seperti mengolah file atau menganalisis gambar, dijalankan di luar event loop. Telemetry dipasang lewat kode, bukan lewat launcher, karena executable hasil bundel tak punya launcher, dan library yang diinstrumentasi ikut dibundel bersama metadata-nya — tanpa metadata itu instrumentasinya mematikan diri tanpa sepatah kata.
Cara saya memeriksa module
Inilah pertanyaan yang saya ajukan pada transaction script service, diambil dari kesepakatan di atas:
| Bidang | Pertanyaan saya | Sumber |
|---|---|---|
| Prosedur | Bisakah saya membaca setiap langkah satu request, berurutan, di satu fungsi endpoint? | satu prosedur per request |
| Urusan | Apakah tiap module dinamai menurut satu urusan, dan menjauh dari lapisan endpoint? | tabel konsep |
| Siklus | Apakah module urusan saling meng-import hanya ke satu arah? | aturan |
| State bersama | Apakah instance tingkat module terbatas pada konfigurasi dan client? | aturan |
| Kerja yang memblokir | Apakah setiap langkah berat berjalan di luar event loop? | aturan |
| Bundel | Apakah executable hasil bundel masih membawa metadata library yang diinstrumentasi dan setiap import dinamis? | aturan |
| Pertumbuhan | Apakah aturan yang sama kini tertulis di lebih dari satu prosedur? | di mana saya tidak memakainya |
Di mana saya tidak memakainya
Ketika aturan tumbuh melampaui satu prosedur
Tandanya duplikasi: keputusan yang sama tertulis di dua endpoint, atau prosedur yang butuh komentar untuk menjelaskan cabang mana berlaku untuk kasus mana. Pada titik itu aturannya pantas punya rumah sendiri, dan service-nya butuh test yang menjalankan aturan itu tanpa jaringan, cache, atau library berat di bawahnya. Itulah yang diberikan Clean Architecture kepada saya, jadi ke sanalah service seperti itu pindah: module urusan menjadi adapter di belakang port, dan tiap prosedur menjadi use case yang bisa dipanggil langsung oleh test.
Ketika service ditulis dalam Rust
Service Rust yang harus cepat berfungsi mendapat bentuk awal yang lain, dengan satu folder per jenis kode dan satu file per operasi, karena sistem module dan compiler Rust menghargai jenis kedataran yang berbeda.
Rujukan
Martin Fowler, Patterns of Enterprise Application Architecture (Addison-Wesley, 2002): pola Transaction Script, dan pola Domain Model yang dijadikannya pembanding, termasuk pembahasannya tentang kapan masing-masing sepadan dengan ongkosnya. Kesepakatan tentang struktur, bundel, dan telemetry yang dijelaskan di sini milik saya sendiri.
Istilah
- Transaction script
- Cara menyusun logika di mana tiap operasi sistem adalah satu prosedur yang mengerjakan seluruh kerjanya sendiri dari awal sampai akhir.
- Domain model
- Cara menyusun logika di mana aturan tinggal di objek yang mewakili bisnisnya, dan prosedur hanya mengoordinasikannya.
- Module urusan
- Module yang memegang kode untuk satu urusan service, misalnya mengambil file atau memverifikasi tanda tangan, dan dinamai menurut urusan itu.
- Event loop
- Satu thread yang dipakai server Python asinkron untuk menangani banyak request sekaligus; memblokirnya membuat semua request tertahan.
- PyInstaller
- Tool yang membundel program Python beserta semua yang di-import-nya menjadi satu executable yang berjalan tanpa instalasi Python terpisah.
Technologies covered
- Python
- FastAPI