127
architectural structural

N-Tier

Reference Wikipedia ↗
N-Tier — class diagram
Plate 127 class diagram

The N-Tier pattern is an architectural pattern that organizes an application into distinct layers, each responsible for a specific aspect of the application. These tiers are logically and physically separated, promoting modularity, maintainability, and scalability. Common tiers include the Presentation Tier (UI), Application Tier (Business Logic), and Data Tier (Data Access), but more tiers can be added as needed.

Usage

The N-Tier pattern is widely used in enterprise application development, web applications, and distributed systems. It’s particularly beneficial when dealing with complex applications that require a clear separation of concerns. Common use cases include: building scalable web services, creating maintainable desktop applications, and developing data-centric applications where data access needs to be abstracted from the business logic. It allows for independent development and deployment of each tier, making updates and changes easier to manage.

Examples

  1. Typical Web Application (e.g., E-commerce Site): A standard e-commerce website often employs an N-Tier architecture. The Presentation Tier is the web browser displaying the product catalog and user interface. The Application Tier (often implemented with frameworks like Spring or Django) handles user authentication, shopping cart management, order processing, and other business rules. The Data Tier manages the product database, user accounts, and order information using a database system like PostgreSQL or MySQL.

  2. Microsoft .NET Applications: The .NET framework encourages the use of N-Tier architectures. A .NET application might have a Presentation Tier built with ASP.NET, an Application Tier containing business logic implemented in C#, and a Data Tier utilizing Entity Framework to interact with a SQL Server database. This separation allows developers to easily swap out the database or UI technology without impacting the core business logic.

Specimens

15 implementations
Specimen 127.01 Dart View specimen ↗

The N-tier pattern organizes an application into distinct layers, each with a specific responsibility. This promotes separation of concerns, making the application more maintainable, testable, and scalable. A typical N-tier architecture includes a presentation tier (UI), a business logic tier (application logic), and a data access tier (database interaction).

This Dart example demonstrates a simple 3-tier architecture for managing user data. The User class represents the data model. The UserService class encapsulates the business logic for user operations. Finally, the UserRepository class handles the data access, simulating database interaction with a simple in-memory list. The separation into these classes, using Dart’s class-based structure, is idiomatic. Dependency Injection is used to loosely couple the tiers, making testing easier. Asynchronous operations (Future) are used for data access, reflecting common Dart practices.

// lib/models/user.dart
class User {
  final String id;
  final String name;
  final String email;

  User({required this.id, required this.name, required this.email});
}

// lib/repositories/user_repository.dart
abstract class UserRepository {
  Future<User> getUserById(String id);
  Future<List<User>> getAllUsers();
  Future<User> createUser(User user);
}

class InMemoryUserRepository implements UserRepository {
  final List<User> _users = [
    User(id: '1', name: 'Alice', email: 'alice@example.com'),
    User(id: '2', name: 'Bob', email: 'bob@example.com'),
  ];

  @override
  Future<User> getUserById(String id) async {
    await Future.delayed(Duration(milliseconds: 50)); // Simulate DB delay
    return _users.firstWhere((user) => user.id == id);
  }

  @override
  Future<List<User>> getAllUsers() async {
    await Future.delayed(Duration(milliseconds: 50));
    return List.unmodifiable(_users);
  }

  @override
  Future<User> createUser(User user) async {
    await Future.delayed(Duration(milliseconds: 50));
    _users.add(user);
    return user;
  }
}

// lib/services/user_service.dart
abstract class UserService {
  Future<User> getUser(String id);
  Future<List<User>> getAllUsers();
  Future<User> createUser(String name, String email);
}

class DefaultUserService implements UserService {
  final UserRepository userRepository;

  DefaultUserService({required this.userRepository});

  @override
  Future<User> getUser(String id) async {
    return userRepository.getUserById(id);
  }

  @override
  Future<List<User>> getAllUsers() async {
    return userRepository.getAllUsers();
  }

  @override
  Future<User> createUser(String name, String email) async {
    final newUser = User(id: 'new-${_users.length}', name: name, email: email);
    return userRepository.createUser(newUser);
  }
}

// lib/main.dart
import 'package:n_tier_example/services/user_service.dart';
import 'package:n_tier_example/repositories/user_repository.dart';

void main() async {
  final userRepository = InMemoryUserRepository();
  final userService = DefaultUserService(userRepository: userRepository);

  final users = await userService.getAllUsers();
  print('All Users:');
  for (var user in users) {
    print('${user.name} (${user.email})');
  }

  final newUser = await userService.createUser('Charlie', 'charlie@example.com');
  print('\nCreated User: ${newUser.name} (${newUser.email})');

  final alice = await userService.getUser('1');
  print('\nUser Alice: ${alice.name} (${alice.email})');
}