Skip to content
IRC-CodingIRC-Coding
3-tier modelpresentation layerDTOservice layerrepository pattern

3-Tier Architecture Explained: UI, Business, Data

3-Tier architecture: presentation, business logic, data access. Components, pros/cons, examples, exam questions.

S

schutzgeist

5 min read
3-Tier Architecture Explained: UI, Business, Data

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

Software Architecture: 3-Layer Model / 3-Tier

This article explains the 3-layer model (3-tier) with exam questions, key points, and tags.

In a Nutshell

The 3-layer model divides software into three logical layers: presentation, business logic, and data access. This separation provides clear responsibilities, easier maintenance, and better testability.

Core Concept

The three layers are:

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

Communication flows top-to-bottom. Each layer only knows the layer directly below it, keeping the system loosely coupled.

Exam-Relevant Key Points

  • Separation of presentation, logic, and data access
  • Clear responsibilities and boundaries
  • Better testability and maintainability
  • Standard in Java/.NET/web projects (IHK-relevant)
  • Components are interchangeable (UI can change)
  • Layers act as security barriers
  • Modularization reduces ongoing costs
  • Architecture must be documented

Core Components

  1. Presentation layer
  2. Logic layer
  3. Data access layer
  4. Interfaces between layers
  5. Logging and error handling
  6. Unit tests in the business layer
  7. DTOs
  8. Security layer
  9. Persistence (SQL/NoSQL)

Practical Example: Task Management in Python

What does this example show? A simple task management system in Python that strictly follows the 3-layer model. Data flows from the presentation layer (CLI) through the business layer (validation, rules) to the persistence layer (SQLite repository).

Why is this a good learning example? Each layer is independently testable: the business layer needs no database, the presentation layer needs no SQLite—they communicate only through clean method calls.

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

# === Layer 3: Persistence (Data Access) ===
@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


# === Layer 2: Business Logic (Application 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("Title cannot be empty")
        if len(title) > 100:
            raise ValueError("Title max 100 characters")
        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 {task_id} not found")
        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"]


# === Layer 1: Presentation (CLI) ===
class TaskCLI:
    def __init__(self, service: TaskService):
        self._service = service

    def run(self):
        while True:
            print("\n1=New Task  2=Open Tasks  3=Complete  4=Exit")
            choice = input("Choice: ")
            if choice == "1":
                title = input("Title: ")
                desc = input("Description: ")
                try:
                    task = self._service.create_task(title, desc)
                    print(f"Task {task.id} created")
                except ValueError as e:
                    print(f"Error: {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("Task ID: "))
                try:
                    self._service.complete_task(tid)
                    print("Completed")
                except ValueError as e:
                    print(f"Error: {e}")
            elif choice == "4":
                break


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

Advantages and Disadvantages

Advantages

  • Structured, maintainable application
  • Easy to swap out components
  • Good testability

Disadvantages

  • More upfront effort
  • Overhead for very small projects

Typical Exam Questions (with Short Answers)

  1. What does the 3-layer model describe? Presentation, logic, and data access.
  2. Which layer validates input? Business logic layer.
  3. What belongs to the data access layer? Database access, SQL/ORM, repositories.

FAQ — Common Questions About the 3-Layer Model

Q: What is the difference between the 3-layer model and 3-tier architecture? A: The 3-layer model describes a logical separation—presentation, business, and persistence—where all layers can run on one server. The 3-tier architecture describes a physical separation across three machines: client, application server, and database server. In exams, both terms are often used interchangeably.

Q: Why shouldn’t you access the database directly from the UI? A: Direct database access from the UI creates tight coupling. Any database schema change requires updates throughout the UI code. Business rules (validations) cannot be centrally managed. SQL injection attacks are harder to defend against. The business layer acts as a central control and security checkpoint.

Q: Where exactly are the boundaries between layers? A: The presentation layer knows only the business layer and passes raw data. The business layer validates and passes DTOs to the persistence layer. The persistence layer knows only SQL/ORM. If you find method names like “validate” or “check” in a repository, the boundary has been violated.

Q: What is a DTO and why do you need it? A: A DTO (Data Transfer Object) carries only data and contains no logic. In the Python example, TaskDTO is a DTO. DTOs decouple layers because each layer only needs to know the DTO format—not the internal structures of other layers.

Q: When is the 3-layer model not worth using? A: For very small scripts (a few hundred lines), the overhead is unnecessary. Once unit tests become important or the database should be replaceable, the separation pays off. A rough rule: single-developer script = flat structure; team project or maintained application = 3 layers.

Further Thoughts

For IHK projects, this model is ideal because it’s straightforward to diagram and justify. Key point: no SQL in controllers, no database access from the UI.

Learning Strategy

  1. Sketch the model for a system (shop, blog).
  2. Implement a CRUD app strictly following the layers.
  3. Explain the layers in your project documentation.
  4. Enforce separation technically through packages or namespaces.

Further Reading

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

Related Posts