099
architectural structural

Layered Architecture

Reference Wikipedia ↗
Layered Architecture — class diagram
Plate 099 class diagram

The Layered Architecture pattern organizes an application into distinct layers, each with a specific role and responsibility. Layers are arranged hierarchically, with each layer building upon the services provided by the layer below. This separation promotes modularity, maintainability, and testability by reducing dependencies and making it easier to modify or replace individual components without affecting the entire system. A strict layered architecture dictates that a layer can only depend on the layer immediately below it.

Usage

Layered Architecture is frequently used in enterprise applications and large-scale software projects. It’s common in web applications (presentation, business logic, data access), desktop applications, and client-server systems. It’s particularly helpful when dealing with complex systems that require clear separation of concerns, making development, debugging, and future enhancements significantly easier. New technologies can be adopted more readily in a specific layer without cascading changes.

Examples

  1. Model-View-Controller (MVC) Frameworks (e.g., Ruby on Rails, Django, Spring MVC): MVC is a specialization of layered architecture. The Model represents the data and business logic, the View handles the presentation layer, and the Controller manages user input and updates the model. These frameworks enforce a clear separation of concerns aligning with the principle of layering.

  2. .NET Framework: The .NET Framework is architected in layers. The Presentation Layer (Windows Forms, WPF, ASP.NET), the Business Logic Layer (application services), the Data Access Layer (ADO.NET, Entity Framework), and the underlying Operating System/Hardware layers work in a hierarchical manner. Developers typically interact primarily with the top layers and can leverage the services of lower layers without needing detailed knowledge of their implementation.

Specimens

15 implementations
Specimen 099.01 Dart View specimen ↗

The Layered Architecture pattern organizes an application into distinct layers, each with a specific responsibility. This promotes separation of concerns, making the application easier to maintain, test, and evolve. Common layers include Presentation (UI), Business Logic (domain), and Data Access (persistence). Requests flow unidirectionally, typically from the Presentation layer down to the Data Access layer. This example demonstrates a simple layered structure for a hypothetical user management system. Dart’s ability to handle both OOP and functional programming makes it suitable; this implements it using classes and interfaces for clear separation.

// lib/presentation/user_presenter.dart
abstract class UserPresenter {
  void displayUsers(List<User> users);
  void displayError(String message);
}

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

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

// lib/domain/user_use_case.dart
abstract class UserUseCase {
  Future<List<User>> getUsers();
}

// lib/data/user_repository.dart
abstract class UserRepository {
  Future<List<User>> fetchUsers();
}

// lib/data/user_repository_impl.dart
class UserRepositoryImpl implements UserRepository {
  // Simulate a data source (e.g., API call, database).
  @override
  Future<List<User>> fetchUsers() async {
    await Future.delayed(const Duration(milliseconds: 500)); // Simulate delay
    return [
      User(id: '1', name: 'Alice'),
      User(id: '2', name: 'Bob'),
    ];
  }
}

// lib/domain/user_use_case_impl.dart
class UserUseCaseImpl implements UserUseCase {
  final UserRepository userRepository;

  UserUseCaseImpl({required this.userRepository});

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

// lib/presentation/user_presenter_impl.dart
class UserPresenterImpl implements UserPresenter {
  @override
  void displayUsers(List<User> users) {
    print('Displaying users:');
    for (var user in users) {
      print('${user.name} (ID: ${user.id})');
    }
  }

  @override
  void displayError(String message) {
    print('Error: $message');
  }
}

// lib/main.dart
import 'package:layered_architecture/presentation/user_presenter.dart';
import 'package:layered_architecture/domain/user_use_case.dart';
import 'package:layered_architecture/data/user_repository.dart';
import 'package:layered_architecture/data/user_repository_impl.dart';
import 'package:layered_architecture/domain/user_use_case_impl.dart';
import 'package:layered_architecture/presentation/user_presenter_impl.dart';

void main() async {
  // Assemble the layers
  final userRepository = UserRepositoryImpl();
  final userUseCase = UserUseCaseImpl(userRepository: userRepository);
  final userPresenter = UserPresenterImpl();

  // Interact through the presentation layer
  try {
    final users = await userUseCase.getUsers();
    userPresenter.displayUsers(users);
  } catch (e) {
    userPresenter.displayError(e.toString());
  }
}