Skip to content
IRC-CodingIRC-Coding
3-Tier архитектураPresentation LayerBusiness Logic LayerData Access LayerDTOService LayerRepository

3-Tier Архитектура: UI, Business, Data Access

3-Tier архитектура: презентация, бизнес-логика, доступ к данным. Компоненты, преимущества, примеры и вопросы.

S

schutzgeist

4 min read
3-Tier Архитектура: UI, Business, Data Access

type: article

keywords:

  • 3-Schichten-Modell
  • 3-Tier Architektur
  • Präsentation Business Daten
  • DTO

canonicalURL: https://www.irc-coding.de/3-schichten-modell-3-tier-architektur

Архитектура ПО: трехслойная модель (3-Tier)

Этот материал посвящен трехслойной архитектуре (3-Tier) с примерами, ключевыми моментами и практическими рекомендациями.

Суть в двух словах

Трехслойная модель разделяет приложение на три логических уровня: представление, бизнес-логика и доступ к данным. Это обеспечивает четкую ответственность, упрощает поддержку и тестирование.

Техническое описание

Три уровня архитектуры:

  1. Presentation (UI)
  2. Business Logic (Services/Use Cases)
  3. Persistence (Repository/ORM/DB)

Данные обычно движутся сверху вниз. Каждый слой знает только о слое, который находится ниже. Это создает слабую связанность между компонентами.

Ключевые моменты для обучения

  • Разделение представления, логики и доступа к данным
  • Четкое распределение ответственности
  • Лучшая тестируемость и поддерживаемость
  • Стандарт для Java, .NET и веб-приложений
  • Возможность замены отдельных компонентов
  • Слои служат границами безопасности
  • Модульность снижает затраты на изменения
  • Архитектура должна быть задокументирована

Основные компоненты

  1. Слой представления
  2. Слой логики
  3. Слой доступа к данным
  4. Интерфейсы между слоями
  5. Логирование и обработка ошибок
  6. Unit-тесты в бизнес-слое
  7. DTO
  8. Слой безопасности
  9. Хранилище (SQL/NoSQL)

Практический пример: управление задачами на Python

Что показывает этот пример? Простая система управления задачами на Python, построенная строго по трехслойной модели. Данные проходят от представления (CLI) через бизнес-слой (валидация, правила) к слою персистентности (SQLite-репозиторий).

Почему это хороший пример для обучения? Каждый слой тестируется отдельно. Бизнес-слой не требует базы данных, представление не требует SQLite — они взаимодействуют только через чистые вызовы методов.

import sqlite3
from dataclasses import dataclass
from typing import List, Optional

# === Слой 3: Persistence (доступ к данным) ===
@dataclass
class TaskDTO:
    id: int; title: str; description: str; status: str

class TaskRepository:
    def __init__(self, db_path="tasks.db"):
        self.db_path = db_path
        with sqlite3.connect(db_path) as conn:
            conn.execute("""
                CREATE TABLE IF NOT EXISTS tasks (
                    id INTEGER PRIMARY KEY AUTOINCREMENT,
                    title TEXT NOT NULL, description TEXT,
                    status TEXT DEFAULT 'open')""")

    def create(self, task: TaskDTO) -> TaskDTO:
        with sqlite3.connect(self.db_path) as conn:
            c = conn.execute("INSERT INTO tasks (title,description,status) VALUES (?,?,?)",
                (task.title, task.description, task.status))
            task.id = c.lastrowid
            return task

    def get_all(self) -> List[TaskDTO]:
        with sqlite3.connect(self.db_path) as conn:
            rows = conn.execute("SELECT * FROM tasks").fetchall()
            return [TaskDTO(*r) for r in rows]

    def get_by_id(self, tid: int) -> Optional[TaskDTO]:
        with sqlite3.connect(self.db_path) as conn:
            r = conn.execute("SELECT * FROM tasks WHERE id=?", (tid,)).fetchone()
            return TaskDTO(*r) if r else None

    def update(self, task: TaskDTO) -> TaskDTO:
        with sqlite3.connect(self.db_path) as conn:
            conn.execute("UPDATE tasks SET title=?,description=?,status=? WHERE id=?",
                (task.title, task.description, task.status, task.id))
            return task

    def delete(self, tid: int) -> bool:
        with sqlite3.connect(self.db_path) as conn:
            return conn.execute("DELETE FROM tasks WHERE id=?", (tid,)).rowcount > 0


# === Слой 2: Business Logic (бизнес-логика) ===
class TaskService:
    def __init__(self, repo: TaskRepository):
        self._repo = repo

    def create_task(self, title: str, description="") -> TaskDTO:
        if not title or len(title.strip()) == 0:
            raise ValueError("Название не может быть пустым")
        if len(title) > 100:
            raise ValueError("Название не более 100 символов")
        return self._repo.create(TaskDTO(0, title.strip(), description, "open"))

    def complete_task(self, task_id: int) -> TaskDTO:
        task = self._repo.get_by_id(task_id)
        if not task:
            raise ValueError(f"Задача {task_id} не найдена")
        task.status = "done"
        return self._repo.update(task)

    def list_open_tasks(self) -> List[TaskDTO]:
        return [t for t in self._repo.get_all() if t.status != "done"]


# === Слой 1: Presentation (CLI) ===
class TaskCLI:
    def __init__(self, service: TaskService):
        self._service = service

    def run(self):
        while True:
            print("\n1=Новая задача  2=Открытые  3=Завершить  4=Выход")
            choice = input("Выбор: ")
            if choice == "1":
                title = input("Название: ")
                desc = input("Описание: ")
                try:
                    task = self._service.create_task(title, desc)
                    print(f"Задача {task.id} создана")
                except ValueError as e:
                    print(f"Ошибка: {e}")
            elif choice == "2":
                for t in self._service.list_open_tasks():
                    print(f"[{t.id}] {t.title} ({t.status})")
            elif choice == "3":
                tid = int(input("ID задачи: "))
                try:
                    self._service.complete_task(tid)
                    print("Завершено")
                except ValueError as e:
                    print(f"Ошибка: {e}")
            elif choice == "4":
                break


if __name__ == "__main__":
    repo = TaskRepository()
    service = TaskService(repo)
    cli = TaskCLI(service)
    cli.run()

Преимущества и недостатки

Преимущества

  • Структурированное, поддерживаемое приложение
  • Легкая замена отдельных компонентов
  • Хорошая тестируемость

Недостатки

  • Больше кода на старте
  • Избыточность в очень маленьких проектах

Типовые вопросы экзамена (с кратким ответом)

  1. Что описывает трехслойная модель? Представление, логика, доступ к данным.
  2. Какой слой выполняет валидацию? Бизнес-логика.
  3. Что входит в слой доступа к данным? DB-операции, SQL/ORM, репозитории.

FAQ — часто задаваемые вопросы о трехслойной модели

В: Какая разница между трехслойной моделью и 3-Tier архитектурой? О: Трехслойная модель описывает логическое разделение (представление, бизнес, персистентность) — все слои могут работать на одном сервере. 3-Tier архитектура описывает физическое разделение на три машины: клиент, сервер приложений и сервер БД. На экзаменах оба термина часто используют как синонимы.

В: Почему нельзя обращаться из UI напрямую к БД? О: Прямые обращения создают сильную связанность. Изменения схемы БД требуют правок во всех местах UI. Бизнес-правила (валидация) нельзя централизовано контролировать. Сложнее защищаться от SQL-injection. Бизнес-слой выступает как центральная точка контроля и защиты.

В: Где точные границы между слоями? О: Представление знает только бизнес-слой и передает сырые данные. Бизнес-слой валидирует и передает DTO в слой персистентности. Персистентность работает только с SQL/ORM. Если в репозитории есть методы вроде “валидировать” или “проверить”, граница нарушена.

В: Что такое DTO и зачем оно нужно? О: DTO (Data Transfer Object) содержит только данные, без логики. В примере Python это TaskDTO. DTO разделяют слои, так как каждый слой знает только формат DTO, но не внутренние структуры других слоев.

В: Когда трехслойная модель не нужна? О: В очень маленьких скриптах (несколько сотен строк) архитектура избыточна. Как только нужны unit-тесты или БД должна быть заменяемой, разделение окупается. Правило: скрипт одного человека = плоская структура, командный проект или приложение = 3 слоя.

Собственный опыт

Для проектов на экзаменах эта модель идеальна, потому что легко рисуется и объясняется. Важно: никаких SQL в контроллере и доступа к БД из UI.

Стратегия обучения

  1. Нарисуйте модель для какой-то системы (магазин, блог).
  2. Реализуйте CRUD-приложение строго по слоям.
  3. Объясните разделение в документации проекта.
  4. Реализуйте разделение через пакеты и пространства имен.

Дополнительные ресурсы

  1. https://c4model.com/
Back to Blog
Share: