• Что бы вступить в ряды "Принятый кодер" Вам нужно:
    Написать 10 полезных сообщений или тем и Получить 10 симпатий.
    Для того кто не хочет терять время,может пожертвовать средства для поддержки сервеса, и вступить в ряды VIP на месяц, дополнительная информация в лс.

  • Пользаватели которые будут спамить, уходят в бан без предупреждения. Спам сообщения определяется администрацией и модератором.

  • Гость, Что бы Вы хотели увидеть на нашем Форуме? Изложить свои идеи и пожелания по улучшению форума Вы можете поделиться с нами здесь. ----> Перейдите сюда
  • Все пользователи не прошедшие проверку электронной почты будут заблокированы. Все вопросы с разблокировкой обращайтесь по адресу электронной почте : info@guardianelinks.com . Не пришло сообщение о проверке или о сбросе также сообщите нам.

Port and Adapter Architecture in Real Life (with Java)

Sascha Оффлайн

Sascha

Заместитель Администратора
Команда форума
Администратор
Регистрация
9 Май 2015
Сообщения
1,483
Баллы
155


Modern software development often faces one major hurdle: tight coupling. Whether you're swapping out a database, switching from REST to GraphQL, or integrating a new third-party service, making changes without breaking the core business logic can feel like performing surgery with a sledgehammer.

Port and Adapter Architecture, also known as Hexagonal Architecture, was created to solve this exact problem. It puts the core logic at the center and treats all external dependencies—databases, UIs, APIs—as plug-and-play accessories.

In this blog post, we’ll explore this architecture with an intuitive analogy, compare it to traditional layered architecture, and implement a Task Manager App in Java that retrieves tasks for users.

What is Port and Adapter Architecture?


Think of the application core as the brain of your app. It defines what the app does, not how it's done. To interface with the messy outside world, it uses:

Ports: Abstract interfaces that define how external actors interact with the app. Think of ports like the USB ports on your laptop—they stay the same regardless of the device you plug in.

Adapters: Concrete implementations that plug into ports and use specific technologies. Like USB cables, you use different ones depending on the kind of device (application or service) you're connecting.

Configurator: The glue that wires everything together at runtime.

Even if your cable (adapter) changes, your port remains the same. It's this stability that makes the architecture so flexible and powerful.

🏠 Real-World Analogy: Home Appliances


House (Core) = Your application's business logic

Sockets (Ports) = Interfaces that define what’s possible

Appliances (Adapters) = Implementations using those sockets (e.g., toaster, TV)

Electrician (Configurator) = Connects the right appliance to the right socket

Need to switch from a toaster to a microwave? You don’t rebuild the house—you just plug in a new appliance.

🔁 Compared to Layered Architecture


Layered architecture often leads to unnecessary and tight coupling between layers. Typically, applications follow a flow like:

UI → Service → Repository → DB

However, in many real-world cases, the boundaries blur. The business logic starts depending directly on the database entities and infrastructure-specific components. For example, ORM entities like JPA are often available throughout the service and UI layers, exposing technical details such as lazy loading and transaction management to places they shouldn't be.

This not only leads to subtle bugs (like uninitialized collections being accessed from the UI) but also makes it difficult to test business logic in isolation—because it ends up entangled with database access logic.

Worse still, updating infrastructure components—like changing the ORM framework or database version—requires changes across all layers, which becomes a bottleneck and is often neglected due to the high cost.

In contrast, Hexagonal Architecture flips this structure. The application core depends only on ports (interfaces). External systems—whether databases, REST APIs, or UIs—connect via adapters. This keeps the core pure and decoupled from implementation details, enabling isolated testing and easier tech upgrades.. The app core only talks to ports. Everything else plugs in.

🎯 Our Example: Task Manager App


Use Case: "Fetch all pending tasks for a user"

We’ll build it using Port and Adapter architecture. Components:


  1. Port (interface)


  2. Adapter (simulated database)


  3. Application Service (core logic)


  4. Configurator (wiring)


  5. Client (runner)
Step 1: Define the Port


public interface TaskRetrievalPort {
List<Task> getPendingTasksForUser(String userId);
}





This interface represents a contract. The service layer only depends on this.

Step 2: Create the Adapter (Simulating a DB)


public class InMemoryTaskAdapter implements TaskRetrievalPort {
private final Map<String, List<Task>> db = new HashMap<>();

public InMemoryTaskAdapter() {
db.put("user1", List.of(
new Task("1", "Buy milk", true),
new Task("2", "Read book", false)
));
}

@Override
public List<Task> getPendingTasksForUser(String userId) {
return db.getOrDefault(userId, List.of()).stream()
.filter(task -> !task.isCompleted())
.collect(Collectors.toList());
}
}



Step 3: Application Core Service


public class TaskService {
private final TaskRetrievalPort taskPort;

public TaskService(TaskRetrievalPort taskPort) {
this.taskPort = taskPort;
}

public List<Task> getUserPendingTasks(String userId) {
return taskPort.getPendingTasksForUser(userId);
}
}



Step 4: Configurator


public class TaskServiceConfigurator {
public static TaskService createDefaultService() {
TaskRetrievalPort adapter = new InMemoryTaskAdapter();
return new TaskService(adapter);
}
}




Step 5: Task Model + Main Client


public class Task {
private final String id;
private final String description;
private final boolean completed;

public Task(String id, String description, boolean completed) {
this.id = id;
this.description = description;
this.completed = completed;
}

public boolean isCompleted() { return completed; }
public String getDescription() { return description; }
}

public class Main {
public static void main(String[] args) {
TaskService service = TaskServiceConfigurator.createDefaultService();
List<Task> tasks = service.getUserPendingTasks("user1");

tasks.forEach(task -> System.out.println("Pending: " + task.getDescription()));
}
}



✅ Benefits in Practice


Want to add a real DB later? Just create a new adapter.
Need to expose tasks via REST? Add a REST adapter.
Writing tests? Mock the port interface.

No changes to TaskService are needed.

📐 Testing Support


Hexagonal architecture makes testing clean and painless:

Unit tests talk to primary ports.

Mocks can replace secondary ports (e.g., database or notification systems).

Adapters can be tested separately with stubs or integration tools like TestContainers.

🧩 Closing Thoughts


Port and Adapter architecture doesn’t just help you write cleaner code—it empowers you to:

Delay infrastructure decisions
Embrace change confidently
Build with clarity and testability

Start small: wrap your use cases in interfaces, implement them with adapters, and connect them using a configurator. It’s a mindset shift—but one that pays off in the long run.



Источник:

Пожалуйста Авторизируйтесь или Зарегистрируйтесь для просмотра скрытого текста.

 
Вверх Снизу