Layered Architecture
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
-
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.
-
.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 implementationsThe 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());
}
}
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 and test. Common layers include Presentation (UI), Business Logic (application core), and Data Access (persistence). My Scala example models a simple order processing system. The OrderService layer encapsulates order creation and validation logic. The OrderRepository handles persistence (in this case, a simple in-memory map). The OrderController acts as the presentation layer, receiving input and orchestrating operations. Using traits for layers and dependency injection allows for flexible testing and potential future swapping of implementations – a hallmark of functional Scala design.
// Domain Layer: Represents the business entities
case class Order(id: Int, items: List[String], total: Double)
// Data Access Layer: Handles persistence
trait OrderRepository {
def save(order: Order): Unit
def get(id: Int): Option[Order]
def getAll: List[Order]
}
class InMemoryOrderRepository extends OrderRepository {
private var orders: Map[Int, Order] = Map()
var nextId = 1
override def save(order: Order): Unit = {
orders = orders + (order.id -> order)
nextId += 1
}
override def get(id: Int): Option[Order] = orders.get(id)
override def getAll: List[Order] = orders.values.toList
}
// Business Logic Layer: Contains the core application logic
trait OrderService {
def createOrder(items: List[String]): Order
def getOrder(id: Int): Option[Order]
def getAllOrders: List[Order]
}
class DefaultOrderService(orderRepository: OrderRepository) extends OrderService {
override def createOrder(items: List[String]): Order = {
val total = items.size * 10.0 // Simplified price calculation
val order = Order(orderRepository.getAll.size + 1, items, total)
orderRepository.save(order)
order
}
override def getOrder(id: Int): Option[Order] = orderRepository.get(id)
override def getAllOrders: List[Order] = orderRepository.getAll
}
// Presentation Layer: Handles user interaction
trait OrderController {
def createOrder(items: List[String]): Order
def getOrder(id: Int): Option[Order]
def getAllOrders: List[Order]
}
class OrderControllerImpl(orderService: OrderService) extends OrderController {
override def createOrder(items: List[String]): Order = orderService.createOrder(items)
override def getOrder(id: Int): Option[Order] = orderService.getOrder(id)
override def getAllOrders: List[Order] = orderService.getAllOrders
}
// Main Application (Illustrative)
object Main extends App {
val repository = new InMemoryOrderRepository()
val service = new DefaultOrderService(repository)
val controller = new OrderControllerImpl(service)
val order1 = controller.createOrder(List("apple", "banana"))
val order2 = controller.createOrder(List("orange", "grape", "kiwi"))
println(s"Order 1: $order1")
println(s"Order 2: ${controller.getOrder(2)}")
println(s"All Orders: ${controller.getAllOrders}")
}
The Layered Architecture pattern organizes an application into distinct layers, each with a specific responsibility. This separation concerns allows for easier maintenance, testing, and modification – changes in one layer ideally don’t affect others. Common layers include Presentation (UI), Application (Business Logic), and Data (Data Access).
My PHP implementation showcases these layers. The Presentation layer (handled through a simple controller) receives input and displays output. The Application layer (UserService) encapsulates business rules regarding users. The Data layer (UserRepository) is responsible for database interactions. Dependency Injection is used to connect the layers, promoting loose coupling and testability – making this implementation a standard approach in many PHP frameworks.
<?php
// Data Layer: UserRepository
class UserRepository {
private $db;
public function __construct($db) {
$this->db = $db; // Assume $db is a PDO connection
}
public function getAllUsers(): array {
$stmt = $this->db->query("SELECT * FROM users");
return $stmt->fetchAll(PDO::FETCH_ASSOC);
}
public function getUserById(int $id): ?array {
$stmt = $this->db->prepare("SELECT * FROM users WHERE id = :id");
$stmt->bindParam(':id', $id, PDO::PARAM_INT);
$stmt->execute();
return $stmt->fetch(PDO::FETCH_ASSOC);
}
}
// Application Layer: UserService
class UserService {
private $userRepository;
public function __construct(UserRepository $userRepository) {
$this->userRepository = $userRepository;
}
public function getAllUsers(): array {
return $this->userRepository->getAllUsers();
}
public function getUserById(int $id): ?array {
return $this->userRepository->getUserById($id);
}
}
// Presentation Layer: UserController
class UserController {
private $userService;
public function __construct(UserService $userService) {
$this->userService = $userService;
}
public function index(): void {
$users = $this->userService->getAllUsers();
// In a real application, this would render a view (e.g., using Blade)
echo "<pre>";
print_r($users);
echo "</pre>";
}
public function show(int $id): void {
$user = $this->userService->getUserById($id);
// In a real application, this would render a view
echo "<pre>";
print_r($user);
echo "</pre>";
}
}
// Setup (for demonstration purposes)
try {
$db = new PDO('sqlite::memory:');
$db->exec("CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT)");
$db->exec("INSERT INTO users (name) VALUES ('Alice')");
$db->exec("INSERT INTO users (name) VALUES ('Bob')");
$userRepository = new UserRepository($db);
$userService = new UserService($userRepository);
$userController = new UserController($userService);
$userController->index(); // List all users
$userController->show(1); // Show user with ID 1
} catch (PDOException $e) {
echo "Database error: " . $e->getMessage();
}
The Layered Architecture pattern organizes an application into distinct layers, each performing a specific role. This promotes separation of concerns and makes the system more maintainable and testable. Common layers include Presentation, Business Logic, and Data Access. Our Ruby example utilizes classes to represent each layer. The Application class orchestrates interactions between them. Data flows unidirectionally – Presentation calls Business Logic, which in turn calls Data Access. This implementation emphasizes Ruby’s object-oriented capabilities, using clear class definitions and method calls for communication. It eschews complex configuration in favor of direct object instantiation, aligning with Ruby’s focus on simplicity and readability.
# app/application.rb
class Application
def initialize(business_logic, presentation)
@business_logic = business_logic
@presentation = presentation
end
def run(request)
result = @business_logic.process(request)
@presentation.display(result)
end
end
# app/business_logic.rb
class BusinessLogic
def initialize(data_access)
@data_access = data_access
end
def process(request)
data = @data_access.fetch_data(request)
# Perform some business logic on the data
"Business Logic says: #{data.upcase}"
end
end
# app/data_access.rb
class DataAccess
def fetch_data(request)
# Simulate fetching data from a database or external source
if request == "get_data"
"some data"
else
"default data"
end
end
end
# app/presentation.rb
class Presentation
def display(data)
puts data
end
end
# main.rb
require_relative 'app/application'
require_relative 'app/business_logic'
require_relative 'app/data_access'
require_relative 'app/presentation'
data_access = DataAccess.new
business_logic = BusinessLogic.new(data_access)
presentation = Presentation.new
application = Application.new(business_logic, presentation)
application.run("get_data")
application.run("other_request")
The Layered Architecture pattern organizes an application into distinct layers, each with a specific responsibility. This promotes separation of concerns, making the code more maintainable, testable, and adaptable to change. Common layers include Presentation (UI), Business Logic (Use Cases), Data Access (Repositories), and potentially Entity/Domain layers.
This Swift example demonstrates a simplified layered architecture for managing user data. The User struct represents the domain entity. UserRepository handles data persistence (mocked here, but would interact with a database or API). UserUseCases encapsulates the application’s business rules relating to users (e.g., creating a user). Finally, a UserViewController (or similar) would represent the presentation layer, interacting only with the Use Cases. The code uses protocols for dependency injection, aligning with Swift’s emphasis on design flexibility and testability. Structs are used for data models, a common and performant Swift practice.
// MARK: - Entity Layer
struct User {
let id: Int
let name: String
let email: String
}
// MARK: - Data Access Layer
protocol UserRepository {
func getUser(withId id: Int) -> User?
func saveUser(user: User)
}
struct MockUserRepository: UserRepository {
private var users: [User] = []
func getUser(withId id: Int) -> User? {
return users.first { $0.id == id }
}
func saveUser(user: User) {
users.append(user)
}
}
// MARK: - Business Logic Layer
protocol UserUseCases {
func createUser(name: String, email: String) -> User
func getUserDetails(userId: Int) -> User?
}
struct DefaultUserUseCases: UserUseCases {
private let userRepository: UserRepository
init(userRepository: UserRepository) {
self.userRepository = userRepository
}
func createUser(name: String, email: String) -> User {
let newUser = User(id: Int.random(in: 1000...9999), name: name, email: email)
userRepository.saveUser(user: newUser)
return newUser
}
func getUserDetails(userId: Int) -> User? {
return userRepository.getUser(withId: userId)
}
}
// MARK: - Presentation Layer (Simplified)
class UserViewController {
private let useCases: UserUseCases
init(useCases: UserUseCases) {
self.useCases = useCases
}
func createUser(name: String, email: String) -> User {
return useCases.createUser(name: name, email: email)
}
func getUser(userId: Int) -> User? {
return useCases.getUserDetails(userId: userId)
}
}
// MARK: - Example Usage
let userRepository = MockUserRepository()
let userUseCases = DefaultUserUseCases(userRepository: userRepository)
let userViewController = UserViewController(useCases: userUseCases)
let newUser = userViewController.createUser(name: "Alice", email: "alice@example.com")
print("Created User: \(newUser)")
if let retrievedUser = userViewController.getUser(userId: newUser.id) {
print("Retrieved User: \(retrievedUser)")
}
The Layered Architecture pattern organizes an application into distinct layers, each with a specific role and responsibility. This promotes separation of concerns, making the application more maintainable, testable, and adaptable to change. Common layers include Presentation, Business Logic, and Data Access. My Kotlin example demonstrates this with UserInterface, UserService, and UserRepository. UserService orchestrates data processing, calling UserRepository for data persistence and potentially other services. The UserInterface (here, a simple function) interacts only with UserService, effectively isolating it from direct database interaction. This aligns with Kotlin’s preference for concise code and functional decomposition where appropriate, while still allowing object-oriented structuring of concerns.
// Domain layer: Data class representing a user
data class User(val id: Int, val name: String, val email: String)
// Business Logic Layer: Handles user-related operations
class UserService {
private val userRepository = UserRepository()
fun getUserById(id: Int): User? {
return userRepository.getUserById(id)
}
fun createUser(name: String, email: String): User {
val newUser = User(0, name, email)
return userRepository.createUser(newUser)
}
}
// Data Access Layer: Interacts with the data source (e.g., database)
class UserRepository {
// In a real application, this would interact with a database.
private val users = mutableListOf<User>()
private var nextId = 1
fun getUserById(id: Int): User? {
return users.find { it.id == id }
}
fun createUser(user: User): User {
val newUser = user.copy(id = nextId++)
users.add(newUser)
return newUser
}
}
// Presentation Layer: Exposes the application's functionality
fun main() {
val userService = UserService()
// Create a user
val newUser = userService.createUser("Alice", "alice@example.com")
println("Created user: $newUser")
// Get the user by ID
val retrievedUser = userService.getUserById(newUser.id)
println("Retrieved user: $retrievedUser")
}
The Layered Architecture pattern organizes an application into distinct layers, each performing a specific role. This promotes separation of concerns, making the application more maintainable, testable, and adaptable to change. Typically, layers include a Presentation (UI), Business Logic, and Data Access layer. My Rust example models these three key layers. The main function represents the Presentation layer, calling functions from the business_logic module. This module orchestrates data operations via the data_access module. Using modules is Rust’s primary mechanism for code organization and encapsulation, fitting the pattern’s intent well. Data flows unidirectionally, ensuring layers remain independent.
// src/main.rs - Presentation Layer
mod business_logic;
fn main() {
let result = business_logic::process_data(10, 5);
println!("Result: {}", result);
}
// src/business_logic.rs - Business Logic Layer
mod data_access;
pub fn process_data(x: i32, y: i32) -> i32 {
// Perform some business logic
let data = data_access::get_data(x);
data + y
}
// src/data_access.rs - Data Access Layer
pub fn get_data(id: i32) -> i32 {
// Simulate fetching data from a database or other source
// In a real application, this would handle persistence logic
match id {
10 => 20,
_ => 0,
}
}
The Layered Architecture pattern organizes an application into distinct layers, each performing a specific role. This promotes separation of concerns, making the application more maintainable and testable. Layers typically include Presentation (UI), Business Logic, Data Access, and potentially others like External Services. Each layer only interacts with the layers directly below it.
This Go example demonstrates a simple layered architecture for a user service. The domain layer contains core business logic (user struct & validation). The service layer orchestrates these domain operations and potentially includes application-specific rules. The repository layer handles data access (in this case, a simple in-memory user store). The main function represents the Presentation layer, interacting with the service. This structure leverages Go’s package system for clear separation, and interfaces for decoupling layers enabling testability and future adaptability.
// main.go - Presentation Layer
package main
import (
"fmt"
"log"
"github.com/your-username/layered-go/service"
"github.com/your-username/layered-go/repository"
)
func main() {
userRepo := repository.NewUserRepository()
userService := service.NewUserService(userRepo)
// Create a user (Presentation -> Service -> Repository)
user, err := userService.CreateUser("Alice", "alice@example.com")
if err != nil {
log.Fatalf("Error creating user: %v", err)
}
fmt.Printf("Created User: %+v\n", user)
// Get a user (Presentation -> Service -> Repository)
retrievedUser, err := userService.GetUser(user.ID)
if err != nil {
log.Fatalf("Error getting user: %v", err)
}
fmt.Printf("Retrieved User: %+v\n", retrievedUser)
}
// domain/user.go - Domain Layer
package domain
import "fmt"
type User struct {
ID int
Name string
Email string
}
func (u *User) Validate() error {
if u.Name == "" {
return fmt.Errorf("name is required")
}
if u.Email == "" {
return fmt.Errorf("email is required")
}
return nil
}
// service/user_service.go - Service Layer
package service
import (
"github.com/your-username/layered-go/domain"
"github.com/your-username/layered-go/repository"
)
type UserService struct {
userRepository repository.UserRepository
}
func NewUserService(repo repository.UserRepository) *UserService {
return &UserService{userRepository: repo}
}
func (s *UserService) CreateUser(name string, email string) (*domain.User, error) {
user := &domain.User{Name: name, Email: email}
if err := user.Validate(); err != nil {
return nil, err
}
return s.userRepository.Save(user)
}
func (s *UserService) GetUser(id int) (*domain.User, error) {
return s.userRepository.FindByID(id)
}
// repository/user_repository.go - Data Access Layer
package repository
import (
"fmt"
"github.com/your-username/layered-go/domain"
)
type UserRepository interface {
Save(user *domain.User) (*domain.User, error)
FindByID(id int) (*domain.User, error)
}
type InMemoryUserRepository struct {
users map[int]*domain.User
nextID int
}
func NewUserRepository() *InMemoryUserRepository {
return &InMemoryUserRepository{
users: make(map[int]*domain.User),
nextID: 1,
}
}
func (r *InMemoryUserRepository) Save(user *domain.User) (*domain.User, error) {
user.ID = r.nextID
r.users[r.nextID] = user
r.nextID++
return user, nil
}
func (r *InMemoryUserRepository) FindByID(id int) (*domain.User, error) {
user, ok := r.users[id]
if !ok {
return nil, fmt.Errorf("user not found with id %d", id)
}
return user, nil
}
The Layered Architecture pattern organizes an application into distinct layers, each with a specific responsibility. This promotes separation of concerns, making the system easier to maintain, test, and evolve. Typically, layers include a presentation layer (UI), a business logic layer, and a data access layer. Our C example demonstrates this with main.c handling presentation (input/output), business_logic.c containing core functionality, and data_access.c interacting with a simple in-memory “database.” Headers define the interfaces between layers, hiding implementation details. This fits C’s modularity through header files and separate compilation.
// data_access.h
#ifndef DATA_ACCESS_H
#define DATA_ACCESS_H
#include <stdio.h>
int get_data(int id, int *value);
int save_data(int id, int value);
#endif
// data_access.c
#include "data_access.h"
#include <stdlib.h>
#define MAX_DATA 10
static int data[MAX_DATA];
static int data_initialized = 0;
int get_data(int id, int *value) {
if (!data_initialized) {
// Initialize some dummy data
for (int i = 0; i < MAX_DATA; i++) {
data[i] = i * 2;
}
data_initialized = 1;
}
if (id >= 0 && id < MAX_DATA) {
*value = data[id];
return 0; // Success
} else {
return -1; // Failure
}
}
int save_data(int id, int value) {
if (id >= 0 && id < MAX_DATA) {
data[id] = value;
return 0; // Success
} else {
return -1; // Failure
}
}
// business_logic.h
#ifndef BUSINESS_LOGIC_H
#define BUSINESS_LOGIC_H
int process_data(int id, int *result);
#endif
// business_logic.c
#include "business_logic.h"
#include "data_access.h"
int process_data(int id, int *result) {
int value;
if (get_data(id, &value) == 0) {
*result = value * 2;
return 0;
} else {
return -1;
}
}
// main.c
#include <stdio.h>
#include "business_logic.h"
int main() {
int id;
int processed_value;
printf("Enter an ID: ");
scanf("%d", &id);
if (process_data(id, &processed_value) == 0) {
printf("Processed value for ID %d: %d\n", id, processed_value);
} else {
printf("Error processing data for ID %d\n", id);
}
return 0;
}
The Layered Architecture pattern organizes an application into distinct horizontal layers, each performing a specific role. This promotes separation of concerns, making the system more maintainable and testable. Layers typically include a Presentation Layer (UI), a Business Logic Layer, and a Data Access Layer. Each layer only interacts with the layers immediately above and below it.
This C++ example demonstrates a simplified layered architecture for managing user accounts. The PresentationLayer handles user interaction, the BusinessLogicLayer contains account-related business rules (like validation), and the DataAccessLayer interacts with a simple in-memory database. The use of interfaces (IAccountRepository, IAccountService) is a common C++ practice for decoupling layers and allowing for easier dependency injection and testing. This approach is readily adaptable to more complex scenarios with real databases and applications.
#include <iostream>
#include <vector>
#include <string>
#include <algorithm>
// Data Access Layer
class IAccountRepository {
public:
virtual bool saveAccount(const std::string& username, const std::string& password) = 0;
virtual bool authenticateAccount(const std::string& username, const std::string& password) = 0;
virtual ~IAccountRepository() = default;
};
class InMemoryAccountRepository : public IAccountRepository {
private:
std::vector<std::pair<std::string, std::string>> accounts;
public:
bool saveAccount(const std::string& username, const std::string& password) override {
accounts.push_back({username, password});
return true;
}
bool authenticateAccount(const std::string& username, const std::string& password) override {
return std::any_of(accounts.begin(), accounts.end(),
[&](const auto& account){ return account.first == username && account.second == password; });
}
};
// Business Logic Layer
class IAccountService {
public:
virtual bool registerAccount(const std::string& username, const std::string& password) = 0;
virtual bool loginAccount(const std::string& username, const std::string& password) = 0;
virtual ~IAccountService() = default;
};
class AccountService : public IAccountService {
private:
IAccountRepository* repository;
public:
AccountService(IAccountRepository* repo) : repository(repo) {}
bool registerAccount(const std::string& username, const std::string& password) override {
if (username.empty() || password.empty()) return false;
return repository->saveAccount(username, password);
}
bool loginAccount(const std::string& username, const std::string& password) override {
if (username.empty() || password.empty()) return false;
return repository->authenticateAccount(username, password);
}
};
// Presentation Layer
class PresentationLayer {
private:
IAccountService* service;
public:
PresentationLayer(IAccountService* svc) : service(svc) {}
void run() {
std::string username, password;
char choice;
do {
std::cout << "\nMenu:\n";
std::cout << "1. Register\n";
std::cout << "2. Login\n";
std::cout << "3. Exit\n";
std::cout << "Enter your choice: ";
std::cin >> choice;
switch (choice) {
case '1':
std::cout << "Enter username: ";
std::cin >> username;
std::cout << "Enter password: ";
std::cin >> password;
if (service->registerAccount(username, password)) {
std::cout << "Account registered successfully!\n";
} else {
std::cout << "Registration failed.\n";
}
break;
case '2':
std::cout << "Enter username: ";
std::cin >> username;
std::cout << "Enter password: ";
std::cin >> password;
if (service->loginAccount(username, password)) {
std::cout << "Login successful!\n";
} else {
std::cout << "Login failed.\n";
}
break;
case '3':
std::cout << "Exiting...\n";
break;
default:
std::cout << "Invalid choice. Try again.\n";
}
} while (choice != '3');
}
};
int main() {
InMemoryAccountRepository repository;
AccountService service(&repository);
PresentationLayer presentation(&service);
presentation.run();
return 0;
}
The Layered Architecture pattern organizes an application into distinct layers, each with a specific responsibility. This promotes separation of concerns, making the application more maintainable, testable, and adaptable to change. Common layers include Presentation, Business Logic, and Data Access. Our C# example demonstrates a simplified version with these three layers. The PresentationLayer handles user interaction, the BusinessLogicLayer contains the core application rules, and the DataAccessLayer interacts with the data source (in this case, a simple in-memory list). This implementation uses interfaces to decouple layers, a common C# practice for dependency injection and testability.
// DataAccessLayer
namespace MyApp.DataAccess
{
public interface IProductRepository
{
List<string> GetProducts();
}
public class ProductRepository : IProductRepository
{
private readonly List<string> _products = new List<string> { "Apple", "Banana", "Orange" };
public List<string> GetProducts()
{
return _products;
}
}
}
// BusinessLogicLayer
namespace MyApp.BusinessLogic
{
public interface IProductService
{
List<string> GetAllProducts();
}
public class ProductService : IProductService
{
private readonly IProductRepository _productRepository;
public ProductService(IProductRepository productRepository)
{
_productRepository = productRepository;
}
public List<string> GetAllProducts()
{
return _productRepository.GetProducts();
}
}
}
// PresentationLayer
namespace MyApp.Presentation
{
public class ProductController
{
private readonly IProductService _productService;
public ProductController(IProductService productService)
{
_productService = productService;
}
public void DisplayProducts()
{
var products = _productService.GetAllProducts();
foreach (var product in products)
{
System.Console.WriteLine(product);
}
}
}
public class Program
{
public static void Main(string[] args)
{
// Dependency Injection
var productRepository = new MyApp.DataAccess.ProductRepository();
var productService = new MyApp.BusinessLogic.ProductService(productRepository);
var productController = new ProductController(productService);
productController.DisplayProducts();
}
}
}
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, and Data Access. Our TypeScript example demonstrates this with UserController, UserService, and UserRepository. UserController handles HTTP requests, delegating to UserService for business rules. UserService interacts with UserRepository for data persistence. This structure is idiomatic TypeScript due to its use of interfaces (UserRepository) for dependency injection and clear type definitions, enhancing code readability and testability. The separation into classes aligns with TypeScript’s OOP capabilities.
// src/interfaces/user-repository.ts
interface UserRepository {
getUser(id: number): Promise<User | null>;
}
// src/models/user.ts
interface User {
id: number;
name: string;
email: string;
}
// src/services/user-service.ts
class UserService {
constructor(private userRepository: UserRepository) {}
async getUserDetails(id: number): Promise<User | null> {
const user = await this.userRepository.getUser(id);
if (!user) {
return null;
}
// Add business logic here, e.g., data validation, transformation
return user;
}
}
// src/controllers/user-controller.ts
import { Request, Response } from 'express';
import { UserService } from '../services/user-service';
class UserController {
constructor(private userService: UserService) {}
async getUser(req: Request, res: Response): Promise<void> {
const userId = parseInt(req.params.id);
const user = await this.userService.getUserDetails(userId);
if (user) {
res.status(200).json(user);
} else {
res.status(404).json({ message: 'User not found' });
}
}
}
// src/data/in-memory-user-repository.ts
class InMemoryUserRepository implements UserRepository {
private users: User[] = [
{ id: 1, name: 'John Doe', email: 'john.doe@example.com' },
{ id: 2, name: 'Jane Smith', email: 'jane.smith@example.com' },
];
async getUser(id: number): Promise<User | null> {
return this.users.find((user) => user.id === id) || null;
}
}
// src/index.ts
import express from 'express';
import { UserController } from './controllers/user-controller';
import { UserService } from './services/user-service';
import { InMemoryUserRepository } from './data/in-memory-user-repository';
const app = express();
const port = 3000;
const userRepository = new InMemoryUserRepository();
const userService = new UserService(userRepository);
const userController = new UserController(userService);
app.get('/users/:id', userController.getUser);
app.listen(port, () => {
console.log(`Server listening at http://localhost:${port}`);
});
The Layered Architecture pattern organizes an application into distinct layers, each performing a specific role. This promotes separation of concerns, making the application more maintainable and testable. Common layers include Presentation (UI), Business Logic, and Data Access. Our JavaScript example demonstrates this by separating concerns into modules: ui.js handles user interaction, businessLogic.js contains the core application rules, and dataAccess.js manages data persistence (simulated here with a simple array). This modular approach is idiomatic JavaScript, leveraging ES modules for clear dependencies and organization, and avoiding global scope pollution.
// dataAccess.js
let data = [];
export function getData() {
return [...data]; // Return a copy to prevent direct modification
}
export function saveData(item) {
data.push(item);
}
// businessLogic.js
import { getData, saveData } from './dataAccess.js';
export function processData(newItem) {
// Example business rule: Only save if the item is not empty
if (newItem.trim() !== "") {
saveData(newItem);
return true;
}
return false;
}
export function getProcessedData() {
return getData().map(item => item.toUpperCase());
}
// ui.js
import { processData, getProcessedData } from './businessLogic.js';
const inputElement = document.getElementById('dataInput');
const addButton = document.getElementById('addButton');
const outputElement = document.getElementById('dataOutput');
addButton.addEventListener('click', () => {
const inputValue = inputElement.value;
if (processData(inputValue)) {
outputElement.textContent = getProcessedData().join(', ');
inputElement.value = '';
} else {
outputElement.textContent = "Input cannot be empty.";
}
});
The Layered Architecture pattern organizes an application into distinct layers, each performing a specific role. This promotes separation of concerns, making the application more maintainable and testable. Typically, layers include Presentation (UI), Business Logic, and Data Access. Requests flow downwards through layers, and responses flow upwards.
This Python example demonstrates a simplified layered structure for a user management system. The presentation_layer handles user interaction (simulated here with print statements). The business_layer contains the core logic for user operations. The data_access_layer interacts with a data store (represented by a simple list). The layers are separated into different modules, and each layer only knows about the layer directly below it, adhering to the principle of loose coupling. This structure is common in Python applications, especially those aiming for scalability and maintainability.
# presentation_layer.py
from business_layer import UserBusinessLogic
def main():
business_logic = UserBusinessLogic()
user_id = 1
user_data = business_logic.get_user(user_id)
if user_data:
print(f"User found: {user_data}")
else:
print("User not found.")
new_user = {"id": 2, "name": "Jane Doe"}
business_logic.create_user(new_user)
print("User created.")
# business_layer.py
from data_access_layer import UserRepository
class UserBusinessLogic:
def __init__(self):
self.user_repository = UserRepository()
def get_user(self, user_id):
return self.user_repository.get_user_by_id(user_id)
def create_user(self, user_data):
self.user_repository.create_user(user_data)
# data_access_layer.py
class UserRepository:
def __init__(self):
self.users = [] # In-memory data store
def get_user_by_id(self, user_id):
for user in self.users:
if user["id"] == user_id:
return user
return None
def create_user(self, user_data):
self.users.append(user_data)
if __name__ == "__main__":
main()
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, Business Logic, and Data Access. Our Java example demonstrates this with UserInterface, UserService, and UserRepository. UserInterface handles user input/output, UserService contains the core business rules related to users, and UserRepository interacts with a data source (simulated here with a simple list). This implementation uses interfaces to further decouple layers, adhering to Java’s dependency injection principles and promoting testability. It’s a common and well-understood approach in Java enterprise applications.
// User Interface Layer
interface UserInterface {
void displayUser(User user);
User getUserInput();
}
class ConsoleUserInterface implements UserInterface {
@Override
public void displayUser(User user) {
System.out.println("User: " + user.getName() + ", Email: " + user.getEmail());
}
@Override
public User getUserInput() {
// Simulate user input
return new User("John Doe", "john.doe@example.com");
}
}
// Business Logic Layer
interface UserService {
User createUser(User user);
User getUser(String email);
}
class DefaultUserService implements UserService {
private final UserRepository userRepository;
public DefaultUserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
@Override
public User createUser(User user) {
return userRepository.save(user);
}
@Override
public User getUser(String email) {
return userRepository.findByEmail(email);
}
}
// Data Access Layer
interface UserRepository {
User save(User user);
User findByEmail(String email);
}
class InMemoryUserRepository implements UserRepository {
private final List<User> users = new ArrayList<>();
@Override
public User save(User user) {
users.add(user);
return user;
}
@Override
public User findByEmail(String email) {
return users.stream()
.filter(user -> user.getEmail().equals(email))
.findFirst()
.orElse(null);
}
}
// Data Model
class User {
private final String name;
private final String email;
public User(String name, String email) {
this.name = name;
this.email = email;
}
public String getName() {
return name;
}
public String getEmail() {
return email;
}
}
// Main Application
public class Main {
public static void main(String[] args) {
UserRepository userRepository = new InMemoryUserRepository();
UserService userService = new DefaultUserService(userRepository);
UserInterface userInterface = new ConsoleUserInterface();
User newUser = userInterface.getUserInput();
User createdUser = userService.createUser(newUser);
userInterface.displayUser(createdUser);
User retrievedUser = userService.getUser(newUser.getEmail());
userInterface.displayUser(retrievedUser);
}
}
import java.util.ArrayList;
import java.util.List;