DEV Community

Cover image for Transaction Script: Patrón simple para lógica de negocio (Catalog of Patterns of EAA — Martin Fowler)
AHMED HASAN AKHTAR OVIEDO
AHMED HASAN AKHTAR OVIEDO

Posted on

Transaction Script: Patrón simple para lógica de negocio (Catalog of Patterns of EAA — Martin Fowler)

Transaction Script: A simple pattern to organize business logic

When building enterprise applications, one of the biggest challenges is how to organize business logic. There are many ways to do this, and Martin Fowler, in his well-known book Patterns of Enterprise Application Architecture (2003), proposed a catalog of patterns to address these problems.

In this article we explore the Transaction Script pattern, one of the simplest and most direct patterns in the catalog, accompanied by a practical example in Python.


What is Transaction Script?

The Transaction Script pattern organizes business logic into individual procedures where each procedure handles a single transaction or request.

In other words:

  • If a client wants to create a reservation → there is a script for that.
  • If a client wants to cancel a reservation → there is another dedicated script.

Each script contains:

  • Input validations.
  • Business rules.
  • Database read/write operations.
  • Transaction commit/rollback.

When to use it?

Transaction Script is ideal when:

  • The application is relatively small.
  • Business rules are simple and easy to describe step-by-step.
  • You want speed in development.
  • It is not yet justified to invest in more complex architectures like Domain Model.

Typical use cases:

  • Rapid prototypes.
  • Simple CRUD applications.
  • Services that process clear, linear requests.

When to avoid it?

Avoid when:

  • The domain is complex and logic begins to be duplicated across scripts.
  • There are many shared rules between operations.
  • You need logic reuse or rich object behavior.

In those cases, migrate to patterns like Domain Model or Service Layer.


Practical example: Simple reservation system

Suppose we are building a small API that allows:

  1. Create a reservation.
  2. List active reservations.
  3. Cancel a reservation.

We'll use Python + Flask + SQLite to demonstrate the pattern.


1. Initialize the database

File db_init.py:

```python import sqlite3 def init_db(path='reservas.db'): conn = sqlite3.connect(path) c = conn.cursor() c.execute(''' CREATE TABLE IF NOT EXISTS reservations ( id INTEGER PRIMARY KEY AUTOINCREMENT, customer_name TEXT NOT NULL, resource TEXT NOT NULL, start_time TEXT NOT NULL, end_time TEXT NOT NULL, status TEXT NOT NULL DEFAULT 'active', created_at TEXT NOT NULL DEFAULT (datetime('now')) ); ''') conn.commit() conn.close() if __name__ == '__main__': init_db() print("Database initialized at reservas.db") ``` 
Enter fullscreen mode Exit fullscreen mode

2. Transaction scripts

File transactions.py:

```python import sqlite3 from contextlib import contextmanager DB_PATH = 'reservas.db' @contextmanager def get_conn(): conn = sqlite3.connect(DB_PATH) try: yield conn conn.commit() except: conn.rollback() raise finally: conn.close() def select_active_reservations(): with get_conn() as conn: c = conn.cursor() c.execute("SELECT id, customer_name, resource, start_time, end_time, status, created_at FROM reservations WHERE status = 'active'") return c.fetchall() def create_reservation_tx(customer_name, resource, start_time, end_time): if start_time >= end_time: raise ValueError("start_time must be before end_time") with get_conn() as conn: c = conn.cursor() c.execute(''' SELECT COUNT(*) FROM reservations WHERE resource = ? AND status = 'active' AND NOT (end_time <= ? OR start_time >= ?) ''', (resource, start_time, end_time)) (overlap_count,) = c.fetchone() if overlap_count > 0: raise ValueError("There is already a reservation that overlaps that time range") c.execute(''' INSERT INTO reservations (customer_name, resource, start_time, end_time) VALUES (?, ?, ?, ?) ''', (customer_name, resource, start_time, end_time)) return c.lastrowid def cancel_reservation_tx(reservation_id): with get_conn() as conn: c = conn.cursor() c.execute('SELECT status FROM reservations WHERE id = ?', (reservation_id,)) row = c.fetchone() if not row: raise ValueError("Reservation not found") if row[0] != 'active': raise ValueError("The reservation is not active") c.execute('UPDATE reservations SET status = ? WHERE id = ?', ('cancelled', reservation_id)) return True ``` 
Enter fullscreen mode Exit fullscreen mode

3. Flask API

File app.py:

```python from flask import Flask, request, jsonify from transactions import create_reservation_tx, cancel_reservation_tx, select_active_reservations from db_init import init_db app = Flask(__name__) init_db() @app.route('/reservations', methods=['POST']) def create_reservation(): payload = request.get_json() try: res_id = create_reservation_tx( customer_name=payload['customer_name'], resource=payload['resource'], start_time=payload['start_time'], end_time=payload['end_time'] ) return jsonify({"id": res_id}), 201 except Exception as e: return jsonify({"error": str(e)}), 400 @app.route('/reservations', methods=['GET']) def list_reservations(): rows = select_active_reservations() reservations = [ { "id": r[0], "customer_name": r[1], "resource": r[2], "start_time": r[3], "end_time": r[4], "status": r[5], "created_at": r[6] } for r in rows ] return jsonify(reservations), 200 @app.route('/reservations/<int:res_id>/cancel', methods=['POST']) def cancel_reservation(res_id): try: cancel_reservation_tx(res_id) return jsonify({"status": "cancelled"}), 200 except Exception as e: return jsonify({"error": str(e)}), 400 if __name__ == '__main__': app.run(debug=True) ``` 
Enter fullscreen mode Exit fullscreen mode

4. Quick test with curl

Create a reservation:

```bash curl -X POST http://127.0.0.1:5000/reservations \ -H "Content-Type: application/json" \ -d '{"customer_name":"Ana Perez","resource":"room-A","start_time":"2025-09-20T10:00","end_time":"2025-09-20T11:00"}' ``` 
Enter fullscreen mode Exit fullscreen mode

List reservations:

```bash curl http://127.0.0.1:5000/reservations ``` 
Enter fullscreen mode Exit fullscreen mode

Cancel:

```bash curl -X POST http://127.0.0.1:5000/reservations/1/cancel ``` 
Enter fullscreen mode Exit fullscreen mode

Advantages

  • Simple and direct.
  • Easy for any developer to read.
  • Great for prototypes or small projects.

Disadvantages

  • Tends to duplicate logic as the project grows.
  • Does not scale well for complex domains.
  • Less reusable as rules increase.

Conclusion

The Transaction Script pattern is an excellent entry point to designing enterprise applications. If your system is small, this pattern brings clarity and speed. However, as complexity grows you will likely need to evolve toward other patterns such as Domain Model or Service Layer.

Top comments (0)