در ارتباط با سروکله زدن با دیتابیس، الگوهای متفاوتی وجود داره که چند مورد اصلیش رو بررسی میکنیم:
روش سادهای که در اکثر فریمورکها میبینیم، به این صورت که رکورد دیتابیس رو به صورت آبجکت در اختیار ما میگذاره و به کمک متدها میتونید باهاش کار کنیم. کار کردن باهاش ساده است و پیادهسازی سادهای هم داره. اما ضعفش اینه که دیتاها و لاجیک درهمآمیخته (coupling) میشن.
new_user = User(None, 'john_doe', 'john@example.com') new_user.save()
این الگو، منطق دسترسی به دادهها رو از بقیه اپلیکیشن جدا میکنه و در سطح بالاتری کار با دیتابیس رو ارائه میده. به این صورت پیاده میشه که کار با دیتابیس رو ابسترکت (abstracts) میکنه و به صورت یک اینترفیس (interface) تعریف میشه و اون اینترفیس پیاده سازی و ازش استفاده میشه. با این کار تستپذیری، انعطافپذیری و امکان راحت استفاده از دیتابیسها متفاوت رو پیدا میکنیم. اما از طرفی پیچیدگی پیادهسازی رو بیشتر میکنه. در واقع لاجیک بیزینس کاری نداره که اون سمت دیتابیس چه دیتابیسی قرار داره و چطور کار میکنه و فقط با ابسترکتش سر و کار داره.
این الگو یک لایه برای مجزا کردن آبجکت توی مموری (که نمایانگر داده دیتابیسته، مثلا آبجکت User) از الگو دیتابیس مربوطهاشه (که شامل جداول و ستونها و ارتباطهای جداول و محدودیتهاشونه). استفاده اصلیش اینه که دسترسی به داده و ارتباطش با دیتابیس رو از لاجیک مربوط به برنامه جدا کنه. این الگو باعث میشه لاجیک بیزینس از کد دسترسی دیتابیس جدا کرد. اما خب پیچیدگی پیادهسازی رو بیشتر میکنه. مثالش:
class UserMapper { public function findById($id) { // Database query and mapping logic here } public function save(User $user) { // Database save logic here } }
این الگو معمولا در مورد چارچوب object-relational mapping (ORM) به کار میره، کمک میکنه که تراکنشها (transactions) و عملیاتهای مرتبط با چرخهحیات دیتابیس رو به صورتی مدیریت کنیم که مطمئن بشیم دیتا بینقص و کامله، هدف اصلیش اینه که چندین عملیات دیتابیس در یک تراکنش انجام بشه و مطمئن بشیم که همه تغییرات یا کامیت میشه و یا همه به حالت قبل برمیگردن (roll back).
class UnitOfWork: def __init__(self, database): self.database = database self.connection = None def __enter__(self): self.connection = sqlite3.connect(self.database) return self def __exit__(self, exc_type, exc_value, traceback): if exc_type is None: self.connection.commit() else: self.connection.rollback() self.connection.close() # Example usage with UnitOfWork('mydb.db') as uow: cursor = uow.connection.cursor() cursor.execute('INSERT INTO users (name, email) VALUES (?, ?)', ('John Doe', john@example.com')) cursor.execute('UPDATE users SET name = ? WHERE id = ?', ('Updated Name', 1))
از این الگو برای این استفاده میشه که یک کوئری رو در یک آبجکت قرار بدیم که یکسری پارامتر بگیره و به طور مستقل اجرا بشه. با این کار یکسری کوئری قابل استفاده در جاهای متفاوت داری که نگهداری و تغییرات آینده رو هم راحتتر میکنه.
class UserQuery: def __init__(self, db): self.db = db self.query = "SELECT * FROM users WHERE age = :age" def execute(self, age): # Execute the query with the provided age parameter cursor = self.db.cursor() cursor.execute(self.query, {'age': age}) return cursor.fetchall()
توی این الگو میایم و به ازای هر جدول دیتابیس یک کلاس مشخص تعریف میکنیم و عملیاتهای دسترسی به دادهاش رو تعریف میکنیم.
class UserGateway: def __init__(self, db_connection): self.db_connection = db_connection def find_by_id(self, user_id): pass def create_user(self, name, email): pass def update_user(self, user_id, name, email): pass def delete_user(self, user_id): pass
این الگو هم میاد و یک اینترفیس (interface) برای عملیاتهای دیتابیس تعریف میکنه. درواقع میشه گفت ترکیبی از الگوهایی مثل Repository و Unit of Work میتونه باشه. ولی Repository معمولا سطح بالاتره و با نگاه به منطق بیزینس تعریف میشه، مثلا متدهایی مثل findUserById, getProductByName, or saveOrder اما در DAO میتونه سطح پایینتر و در ارتباط نزدیکتر با دیتابیس و همینطور متدهای کلیتر مثل executeQuery, insertRecord, or updateData باشه.