← Назад к вопросам
Как реализовать пользовательский интерфейс с вычислениями чтобы не дублировалась бизнес-логика в гексагональной архитектуре?
3.0 Senior🔥 101 комментариев
#Архитектура и паттерны
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI3 апр. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Проблема в архитектуре
При неправильной реализации бизнес-логика дублируется между фронтенд и бэкенд. В гексагональной архитектуре нужно четко разделить ответственность.
Решение 1: Разделение ответственности
// backend/domain/invoice.py - бизнес-логика (ОДИН раз)
class InvoiceCalculator:
def calculate_total(self, items, tax_rate):
subtotal = sum(item.price * item.quantity for item in items)
tax = subtotal * tax_rate
return subtotal + tax
// backend/application/invoice_service.py
class InvoiceService:
def __init__(self, repository, calculator):
self.repository = repository
self.calculator = calculator
def create_invoice(self, items, tax_rate):
total = self.calculator.calculate_total(items, tax_rate)
invoice = Invoice(items=items, total=total)
return self.repository.save(invoice)
// backend/presentation/api.py
@router.post("/invoices")
def create_invoice(request):
invoice = invoice_service.create_invoice(request.items, request.tax_rate)
return {
"id": invoice.id,
"total": invoice.total, // Уже вычислено на бэкенде
"items": invoice.items
}
Решение 2: Фронтенд использует API (НЕ дублирует логику)
// frontend/services/invoiceApi.ts
export const invoiceApi = {
createInvoice: async (items, taxRate) => {
const response = await fetch('/api/v1/invoices', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ items, tax_rate: taxRate })
});
return response.json();
}
};
// frontend/hooks/useInvoice.ts
export function useInvoice() {
const [invoice, setInvoice] = useState(null);
const [loading, setLoading] = useState(false);
const createInvoice = async (items, taxRate) => {
setLoading(true);
try {
// Все вычисления на бэкенде!
const data = await invoiceApi.createInvoice(items, taxRate);
setInvoice(data);
} finally {
setLoading(false);
}
};
return { invoice, loading, createInvoice };
}
// frontend/components/InvoiceForm.tsx
export function InvoiceForm() {
const { invoice, loading, createInvoice } = useInvoice();
const handleSubmit = async (items) => {
// НЕ вычисляем total здесь
await createInvoice(items, 0.1);
};
return (
<form onSubmit={handleSubmit}>
{invoice && <p>Total: {invoice.total}</p>}
</form>
);
}
Решение 3: Кэширование результатов без дублирования
// frontend/lib/cache.ts
class CachedInvoiceService {
private cache = new Map();
getInvoice(id) {
return this.cache.get(id) || null;
}
async fetchInvoice(id) {
const cached = this.getInvoice(id);
if (cached) return cached;
const invoice = await invoiceApi.getInvoice(id);
this.cache.set(id, invoice);
return invoice;
}
invalidate(id) {
this.cache.delete(id);
}
}
Решение 4: Отображение формул (только для предпросмотра)
// frontend/components/InvoicePreview.tsx
export function InvoicePreview({ items, taxRate }) {
const [calculation, setCalculation] = useState(null);
// Mock-вычисления ТОЛЬКО для предпросмотра
const calculatePreview = (items, tax) => {
const subtotal = items.reduce((sum, item) => sum + item.price * item.quantity, 0);
const taxAmount = subtotal * tax;
return {
subtotal,
tax: taxAmount,
total: subtotal + taxAmount
};
};
useEffect(() => {
setCalculation(calculatePreview(items, taxRate));
}, [items, taxRate]);
return (
<div>
<p>Total: {calculation?.total}</p>
<button onClick={() => submitInvoice()}>Confirm</button>
</div>
);
}
// submitInvoice вызывает API - бэкенд пересчитает все
async function submitInvoice() {
const result = await invoiceApi.createInvoice(items, taxRate);
displayInvoice(result);
}
Решение 5: Общие типы между фронтом и бэком
// shared/types/invoice.ts
export interface Invoice {
id: string;
items: Item[];
subtotal: number;
tax: number;
total: number;
createdAt: Date;
}
export interface Item {
id: string;
name: string;
price: number;
quantity: number;
}
// frontend/types/invoice.ts
import type { Invoice, Item } from 'shared/types';
const invoice: Invoice = apiResponse;
Ключевые принципы
- Бизнес-логика на бэкенде - вычисления только там
- Фронтенд как представление - отправляет данные, отображает
- API как граница - четкий контракт
- Кэширование для UX - но не дублирование логики
- Один источник истины - бэкенд решает, фронтенд показывает