0
0
Lập trình
Harry Tran
Harry Tran106580903228332612117

Transaction Script: Mẫu đơn giản cho logic kinh doanh

Đăng vào 1 tháng trước

• 9 phút đọc

Transaction Script: Mẫu đơn giản cho logic kinh doanh

Khi xây dựng các ứng dụng doanh nghiệp, một trong những thách thức lớn nhất là cách tổ chức logic kinh doanh. Có nhiều cách để làm điều này, và Martin Fowler, trong cuốn sách nổi tiếng Patterns of Enterprise Application Architecture (2003), đã đề xuất một danh mục các mẫu để giải quyết những vấn đề này.

Trong bài viết này, chúng ta sẽ khám phá mẫu Transaction Script, một trong những mẫu đơn giản và trực tiếp nhất trong danh mục, kèm theo một ví dụ thực tiễn bằng Python.


Transaction Script là gì?

Mẫu Transaction Script tổ chức logic kinh doanh thành các thủ tục riêng biệt, trong đó mỗi thủ tục xử lý một giao dịch hoặc yêu cầu duy nhất.

Nói cách khác:

  • Nếu một khách hàng muốn tạo một đặt chỗ → có một script cho việc đó.
  • Nếu một khách hàng muốn hủy một đặt chỗ → có một script khác dành riêng cho việc đó.

Mỗi script bao gồm:

  • Kiểm tra đầu vào.
  • Quy tắc kinh doanh.
  • Các thao tác đọc/ghi cơ sở dữ liệu.
  • Cam kết/hoàn tác giao dịch.

Khi nào nên sử dụng?

Mẫu Transaction Script lý tưởng khi:

  • Ứng dụng tương đối nhỏ.
  • Các quy tắc kinh doanh đơn giản và dễ mô tả từng bước.
  • Bạn muốn tốc độ trong phát triển.
  • Chưa đủ lý do để đầu tư vào các kiến trúc phức tạp hơn như Domain Model.

Trường hợp sử dụng điển hình:

  • Các nguyên mẫu nhanh.
  • Các ứng dụng CRUD đơn giản.
  • Các dịch vụ xử lý các yêu cầu rõ ràng, tuyến tính.

Khi nào nên tránh?

Tránh khi:

  • Miền (domain) phức tạp và logic bắt đầu bị lặp lại giữa các script.
  • Có nhiều quy tắc chung giữa các thao tác.
  • Bạn cần tái sử dụng logic hoặc hành vi đối tượng phong phú.

Trong những trường hợp đó, nên chuyển sang các mẫu như Domain Model hoặc Service Layer.


Ví dụ thực tiễn: Hệ thống đặt chỗ đơn giản

Giả sử chúng ta đang xây dựng một API nhỏ cho phép:

  1. Tạo một đặt chỗ.
  2. Liệt kê các đặt chỗ đang hoạt động.
  3. Hủy một đặt chỗ.

Chúng ta sẽ sử dụng Python + Flask + SQLite để minh họa mẫu này.


1. Khởi tạo cơ sở dữ liệu

Tạo file db_init.py:

python Copy
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("Cơ sở dữ liệu đã được khởi tạo tại reservas.db")

2. Các script giao dịch

Tạo file transactions.py:

python Copy
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 phải trước 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("Đã có một đặt chỗ trùng thời gian")

        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("Không tìm thấy đặt chỗ")
        if row[0] != 'active':
            raise ValueError("Đặt chỗ không còn hoạt động")
        c.execute('UPDATE reservations SET status = ? WHERE id = ?', ('cancelled', reservation_id))
        return True

3. API Flask

Tạo file app.py:

python Copy
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)

4. Kiểm tra nhanh với curl

Tạo một đặt chỗ:

bash Copy
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"}'

Liệt kê các đặt chỗ:

bash Copy
curl http://127.0.0.1:5000/reservations

Hủy:

bash Copy
curl -X POST http://127.0.0.1:5000/reservations/1/cancel

Ưu điểm

  • Đơn giản và trực tiếp.
  • Dễ đọc cho bất kỳ nhà phát triển nào.
  • Tuyệt vời cho các nguyên mẫu hoặc dự án nhỏ.

Nhược điểm

  • Có xu hướng lặp lại logic khi dự án phát triển.
  • Không mở rộng tốt cho các miền phức tạp.
  • Ít tái sử dụng khi các quy tắc gia tăng.

Kết luận

Mẫu Transaction Script là một điểm khởi đầu tuyệt vời để thiết kế các ứng dụng doanh nghiệp. Nếu hệ thống của bạn nhỏ, mẫu này mang lại sự rõ ràng và tốc độ. Tuy nhiên, khi độ phức tạp tăng lên, bạn sẽ cần phải tiến hóa hướng tới các mẫu khác như Domain Model hoặc Service Layer.

Các thực hành tốt nhất

  • Kiểm tra đầu vào: Luôn kiểm tra dữ liệu đầu vào để tránh lỗi và đảm bảo tính toàn vẹn của dữ liệu.
  • Tách biệt logic: Cố gắng tách biệt logic giữa các script để dễ dàng bảo trì và mở rộng sau này.

Những cạm bẫy phổ biến

  • Lặp lại logic: Đừng để logic bị lặp lại trong các script khác nhau, điều này có thể gây khó khăn trong việc duy trì mã nguồn.
  • Thiếu kiểm tra: Không quên thực hiện kiểm tra cho các trường hợp biên, điều này sẽ giúp phát hiện lỗi sớm hơn.

Mẹo hiệu suất

  • Sử dụng kết nối hiệu quả: Đảm bảo rằng các kết nối cơ sở dữ liệu được quản lý hiệu quả để tránh rò rỉ tài nguyên.
  • Tối ưu hóa truy vấn: Thực hiện tối ưu hóa các truy vấn SQL để cải thiện hiệu suất truy cập dữ liệu.

Phần hỏi đáp

Transaction Script có thể sử dụng cho loại ứng dụng nào?

Mẫu này phù hợp cho các ứng dụng nhỏ, nơi logic kinh doanh không quá phức tạp.

Có nên sử dụng Transaction Script cho các ứng dụng lớn không?

Không nên, vì nó có thể dẫn đến việc lặp lại logic và khó khăn trong bảo trì.

Làm thế nào để chuyển đổi từ Transaction Script sang Domain Model?

Bạn nên bắt đầu bằng cách xác định các đối tượng miền và tách biệt logic của chúng ra khỏi các script giao dịch.

So sánh với các mẫu khác

Mẫu Ưu điểm Nhược điểm
Transaction Script Dễ hiểu, nhanh chóng phát triển Không mở rộng tốt, lặp lại logic
Domain Model Tính tái sử dụng cao, phù hợp với miền phức tạp Cần thời gian phát triển dài hơn
Service Layer Tách biệt rõ ràng giữa các lớp Có thể phức tạp hơn trong cấu trúc
Gợi ý câu hỏi phỏng vấn
Không có dữ liệu

Không có dữ liệu

Bài viết được đề xuất
Bài viết cùng tác giả

Bình luận

Chưa có bình luận nào

Chưa có bình luận nào