Skip to content
Architecture Programming

Transaction Script Service

By Romi Fadli, S.Kom. 8 min read Software Design
On this page
One endpoint procedure as a column of ordered steps, a request entering at the top and the answer leaving at the bottom, each step calling its own concern module beside it

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

AsalPola Transaction Script dari Martin Fowler, dengan kesepakatan saya sendiri untuk service Python
Saya pakai untukService Python kecil dengan satu tugas yang harus cepat berfungsi
Tak saya pakai untukService yang aturan bisnisnya terus tumbuh, atau yang butuh test tanpa infrastrukturnya
BahasaPython, dengan FastAPI atau Flask
Posisi Transaction Script Service di antara arsitektur yang saya pakai.

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:

bash
server.py              # thin entry point: build the app, start uvicorn
src/
├── api.py             # create_app(): middleware, error handlers, one procedure per endpoint
├── config.py          # load configuration once
├── telemetry.py       # traces, logs, and metrics, set up in code
├── fetchers.py        # one module per concern, named after what it does
├── transform.py
└── verifier.py

server.py
routers/               # endpoints grouped by audience: public, internal
services/              # concerns with state or clients: cache, storage, the core engine
utils/                 # configuration, responses, small helpers

Satu endpoint terbaca sebagai daftar langkah yang dikerjakannya, dan tiap langkah langsung memanggil module urusannya:

python
@app.post(f"{prefix}/process")
async def process(request: Request, payload: ProcessPayload):
    request_id = new_request_id()
    token = require_bearer(request)
    secret = decrypt_secret(payload.secret_enc, key=cfg.secret_key)
    document = await fetch_document(session, str(payload.document_url))
    result = await run_in_threadpool(transform, document, secret)
    await upload_bytes(session, str(payload.upload_url), result)
    return ok(request_id, {"size": len(result)})
KonsepTempatnya
Prosedur untuk satu requestsatu fungsi endpoint di api.py atau di router
Kerja milik satu urusanmodule yang dinamai menurut urusannya, seperti fetchers.py atau verifier.py
Konfigurasisatu objek, dimuat sekali dan di-import di mana pun dibutuhkan
Client berumur panjang: sesi HTTP, cacheinstance tingkat module, dibuat sekali
Telemetrymodule tersendiri, diinisialisasi sebelum app dan router-nya dibangun
Titik masukserver.py, yang hanya membangun app dan menjalankan server
Tempat tiap konsep di transaction script service.

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:

BidangPertanyaan sayaSumber
ProsedurBisakah saya membaca setiap langkah satu request, berurutan, di satu fungsi endpoint?satu prosedur per request
UrusanApakah tiap module dinamai menurut satu urusan, dan menjauh dari lapisan endpoint?tabel konsep
SiklusApakah module urusan saling meng-import hanya ke satu arah?aturan
State bersamaApakah instance tingkat module terbatas pada konfigurasi dan client?aturan
Kerja yang memblokirApakah setiap langkah berat berjalan di luar event loop?aturan
BundelApakah executable hasil bundel masih membawa metadata library yang diinstrumentasi dan setiap import dinamis?aturan
PertumbuhanApakah aturan yang sama kini tertulis di lebih dari satu prosedur?di mana saya tidak memakainya
Pertanyaan yang saya pakai untuk memeriksa transaction script service.

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

About the author

Photo of Romi Fadli

Romi Fadli, S.Kom.

Solution Architect · Backend Engineer · Embedded Systems

In my day job I work as a solution architect. I design the system, lead the team that builds it — quality assurance, backend developers, a UI developer, DevOps and DevSecOps — and write the most critical modules myself, such as a certificate authority, a long-term PDF signer, and the core API of a personnel system.

I work across the whole stack that a running system actually needs. On the hardware side that means PCB design, microcontrollers, and embedded firmware — including RISC-V boards running FreeRTOS and Embassy, speaking MQTT over mutually authenticated TLS on a microcontroller with 64 KB of SRAM.

Most of my day-to-day work is software: Rust backends and API modules built on Clean Architecture, an OAuth2/OpenID Connect authorization server written from scratch, PAdES document signing, and certificate authorities — root CAs, timestamping authorities, and OCSP responders. I also build the frontends that sit on top of them.

Running all of that means infrastructure: Docker Compose stacks, networking, Mikrotik and RADIUS, observability with OpenTelemetry, and a steady habit of writing command-line tools and utilities for Linux when the existing ones do not fit.

Share

Enjoyed this?

Get an email when a new article is published.

Referenced by

A locked offline host holding the root key, a signed revocation list crossing an air gap to a small publisher server, which serves the root certificate and the list to a validator

Service kecil yang hanya menerbitkan, menjaga root CA tetap bisa dijangkau tanpa bisa disentuh: ia menyajikan sertifikat root dan daftar pencabutannya, sementara key root dan setiap tanda tangan tetap di host offline — bisa dipakai project mana pun yang punya CA sendiri.

7 min read Read more →
Three camera frames of a face, facing forward, turned right, and turned left, each checked off, flowing into locked private storage that a reviewer looks into

Service Python yang meminta orang di depan kamera menghadap depan, menoleh ke kanan, lalu ke kiri, menangkap satu frame tajam per pose, dan menyimpan ketiganya sebagai bukti privat untuk peninjau manusia — membuktikan orang hidup hadir, bukan siapa dia.

10 min read Read more →
A signed PDF with a QR stamp, wrapped in four nested frames for the signature, its timestamp, the embedded validation data, and the archive timestamp

Service Python yang menandatangani PDF sampai PAdES B-LTA dengan stempel yang terlihat — menanamkan stempel waktu, sertifikat, dan bukti pencabutan, lalu stempel waktu arsip — supaya tanda tangan tetap bisa diverifikasi lama setelah sertifikatnya kedaluwarsa, dan memverifikasi PDF bertanda tangan.

10 min read Read more →

Related Articles

Three camera frames of a face, facing forward, turned right, and turned left, each checked off, flowing into locked private storage that a reviewer looks into

Service Python yang meminta orang di depan kamera menghadap depan, menoleh ke kanan, lalu ke kiri, menangkap satu frame tajam per pose, dan menyimpan ketiganya sebagai bukti privat untuk peninjau manusia — membuktikan orang hidup hadir, bukan siapa dia.

10 min read Read more →
A signed PDF with a QR stamp, wrapped in four nested frames for the signature, its timestamp, the embedded validation data, and the archive timestamp

Service Python yang menandatangani PDF sampai PAdES B-LTA dengan stempel yang terlihat — menanamkan stempel waktu, sertifikat, dan bukti pencabutan, lalu stempel waktu arsip — supaya tanda tangan tetap bisa diverifikasi lama setelah sertifikatnya kedaluwarsa, dan memverifikasi PDF bertanda tangan.

10 min read Read more →