Где в гексагональной архитектуре размещается Frontend?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Frontend в гексагональной архитектуре
Frontend размещается на внешнем кольце (Ports and Adapters) как один из адаптеров для взаимодействия с пользователем. Это критично для понимания архитектуры.
Структура гексагональной архитектуры
┌─────────────────────────────────────────┐
│ EXTERNAL WORLD │
│ (Frontend, Mobile, CLI, Third-party) │
└──────────┬──────────────────────────────┘
│
│ HTTP/REST/GraphQL
│
┌──────────▼────────────────────────────────┐
│ PORTS & ADAPTERS (Boundary Layer) │
│ ┌────────────────────────────────────┐ │
│ │ API Adapter (HTTP/REST) │ │
│ │ - Controllers │ │
│ │ - DTOs │ │
│ │ - Error Handlers │ │
│ └────────────────────────────────────┘ │
│ │
│ Frontend + Mobile + CLI = Adapters │
└──────────┬───────────────────────────────┘
│
│ (Internal Protocol)
│
┌──────────▼───────────────────────────────┐
│ APPLICATION LAYER │
│ - Use Cases / Services │
│ - Business Rules (validation, logic) │
│ - Error Handling │
└──────────┬───────────────────────────────┘
│
│
┌──────────▼───────────────────────────────┐
│ DOMAIN LAYER │
│ - Entities │
│ - Domain Services │
│ - Value Objects │
│ - Domain Rules (pure business logic) │
└──────────┬───────────────────────────────┘
│
│
┌──────────▼───────────────────────────────┐
│ INFRASTRUCTURE LAYER │
│ - Database (Repositories) │
│ - External Services (Email, Payment) │
│ - Config (Environment, Caching) │
└──────────────────────────────────────────┘
Где Frontend в этой схеме
Frontend НЕ часть архитектуры backend.
Frontend это отдельное приложение, которое:
- Взаимодействует с backend через Port (Interface)
- Backend предоставляет Adapter (API endpoint) для Frontend
- Frontend -> HTTP/REST -> API Controller -> Application Layer
Frontend App Backend App
(React/Vue) (FastAPI/Django/Node)
│ │
│ API Request │
├───────────────────>│ Port (HTTP API)
│ │
│ POST /api/users │
│ { name, email } │
│ │
│ ├─> Adapter (Controller)
│ │ - Validate DTO
│ │ - Call Use Case
│ │ - Return Response
│ │
│ ├─> Application Layer
│ │ - CreateUserUseCase
│ │ - Validate business rules
│ │
│ ├─> Domain Layer
│ │ - User Entity
│ │ - Email Value Object
│ │
│<───────────────────┤ Adapter (HTTP Response)
│ { id, name ... } │
│ │
Роли Front и Backend в гексагональной архитектуре
Backend (Hexagonal Architecture):
-
Domain Layer — бизнес-логика
- User entity
- Validation rules
- Business processes
-
Application Layer — use cases
- CreateUserUseCase
- UpdateUserUseCase
- DeleteUserUseCase
-
Ports & Adapters — взаимодействие
- HTTP Port -> REST API Adapter
- WebSocket Port -> WebSocket Adapter
- Database Port -> PostgreSQL Adapter
- Email Port -> SMTP Adapter
Frontend (не часть backend архитектуры):
// Frontend структура (свои слои)
// UI Layer (Presentation)
function UserForm() {}
// Business Logic Layer
function useCreateUser() {}
// API Layer (Adapter to Backend)
const api = {
createUser: (data) => fetch('/api/users', { method: 'POST', body: JSON.stringify(data) })
};
// Frontend НЕ должен повторять backend архитектуру
// Фронту нужна простая структура: Components -> Hooks -> API
Правильное взаимодействие
Backend API контракт (Port):
// backend/ports/http_api.py
class UserCreateRequest(BaseModel):
name: str
email: str
class UserResponse(BaseModel):
id: UUID
name: str
email: str
created_at: datetime
@app.post('/api/users')
class CreateUserController:
def __init__(self, use_case: CreateUserUseCase):
self.use_case = use_case
def handle(self, request: UserCreateRequest) -> UserResponse:
user = self.use_case.execute(request.name, request.email)
return UserResponse.from_domain(user)
Frontend потребляет этот Port:
// frontend/api/users.ts
export const createUser = async (data: { name: string; email: string }) => {
const response = await fetch('https://api.example.com/api/users', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
});
return response.json(); // UserResponse
};
// frontend/hooks/useCreateUser.ts
export function useCreateUser() {
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
const create = async (data) => {
setLoading(true);
try {
const user = await createUser(data);
return user;
} catch (err) {
setError(err);
} finally {
setLoading(false);
}
};
return { create, loading, error };
}
// frontend/components/UserForm.tsx
export function UserForm() {
const { create, loading, error } = useCreateUser();
const handleSubmit = async (e) => {
e.preventDefault();
const user = await create({ name: 'John', email: 'john@example.com' });
};
return (
<form onSubmit={handleSubmit}>
<input type="text" placeholder="Name" />
<input type="email" placeholder="Email" />
<button disabled={loading}>Create</button>
{error && <p>{error.message}</p>}
</form>
);
}
Несколько Ports/Adapters для одного Backend
Backend (Hexagonal)
│
├─ HTTP Port ──> REST API Adapter ──> Frontend (React/Vue)
├─ HTTP Port ──> GraphQL Adapter ──> Mobile App (Apollo)
├─ WebSocket Port ──> WebSocket Adapter ──> Chat App
├─ CLI Port ──> CLI Adapter ──> Admin CLI Tool
└─ Event Bus Port ──> Kafka Adapter ──> Microservices
// Один Domain Layer, много внешних интерфейсов!
Важные принципы
1. Зависимости должны указывать к центру
Frontend (External)
│
├─ Зависит от API
└─ НЕ должна зависеть от Domain Logic
// ПЛОХО — Frontend знает о Domain
const user = new User('John');
user.validate(); // Domain логика во Frontend!
// ХОРОШО — Frontend только UI + API
const response = await api.createUser({ name: 'John' });
const user = response.data;
2. Backend НЕ должен зависеть от Frontend
# ПЛОХО — Backend знает о Frontend структуре
class CreateUserResponse:
def to_react_component(self):
return { ... }
# ХОРОШО — Backend предоставляет Data, Frontend формирует UI
class CreateUserResponse:
id: UUID
name: str
email: str
3. Frontend это Adapter к Domain
Frontend Role = Adapter
├─ Render Domain objects (Users, Posts, Comments)
├─ Collect User Input (Forms)
├─ Send Input to Application Layer (Use Cases)
└─ Display Results (State Management)
Правила взаимодействия Frontend-Backend
Через HTTP Port:
// Frontend отправляет Request DTO
{
"name": "John",
"email": "john@example.com"
}
// Backend возвращает Response DTO
{
"id": "abc-123",
"name": "John",
"email": "john@example.com",
"created_at": "2025-04-02T10:00:00Z"
}
// Frontend преобразует в свою структуру
const user = {
id: response.id,
name: response.name,
email: response.email,
formattedDate: formatDate(response.created_at) // Frontend-specific
};
Вывод
Frontend в гексагональной архитектуре:
- НЕ часть внутренней архитектуры backend
- Является External Actor/Adapter
- Взаимодействует через HTTP Port (REST API)
- Потребляет API контракт (Request/Response DTOs)
- Не повторяет backend архитектуру (не нужны Domain entities в Frontend)
Фrontend = клиент, Backend = сервер. Гексагональная архитектура описывает внутреннее устройство сервера, а Frontend это просто один из адаптеров для взаимодействия с пользователем.