Перебудувати попередній проєкт «Кінопошук» у Clean Architecture та розділити backend і frontend.
Backend:
- ASP.NET Core Web API;
- Clean Architecture;
- Entity Framework Core;
- Repository / Unit of Work;
- DTO;
- Dependency Injection;
- бізнес-логіка в Application.
Frontend:
- React;
- робота з Web API через HTTP;
- усі UI та сторінки реалізуються в React.
Основний акцент роботи — структура Solution та правильні залежності між проєктами.
Створити приблизно таку структуру:
MovieSearch.sln
│
├── MovieSearch.Domain
│
├── MovieSearch.Application
│
├── MovieSearch.Infrastructure
│
├── MovieSearch.WebApi
│
└── movie-search-client
Останній проєкт є окремим React-проєктом і не має Project Reference на .NET-проєкти.
Головна частина завдання — правильно налаштувати залежності.
HTTP / JSON
┌──────────────────────────────┐
│ React Frontend │
│ movie-search-client │
└──────────────┬───────────────┘
│
│ HTTP
▼
┌──────────────────────────────┐
│ MovieSearch.WebApi │
│ Controllers / HTTP / DI │
└──────────────┬───────────────┘
│
▼
┌──────────────────────────────┐
│ MovieSearch.Application │
│ DTO / Services / Use Cases │
└──────────────┬───────────────┘
│
▼
┌──────────────────────────────┐
│ MovieSearch.Domain │
│ Entities / Domain contracts │
└──────────────────────────────┘
▲
│
┌──────────────┴───────────────┐
│ MovieSearch.Infrastructure │
│ EF Core / SQL / Repositories │
│ File System / implementations│
└──────────────────────────────┘
Правила:
Domain
└── не залежить від інших проєктів
Application
└── залежить від Domain
Infrastructure
└── залежить від Application та Domain
WebApi
└── використовує Application
└── підключає Infrastructure у Composition Root
React
└── не має залежностей від .NET-проєктів
└── працює тільки через HTTP API
Важливо:
React не є частиною .NET Solution у сенсі Project Reference. Це окремий frontend, який взаємодіє з backend через HTTP/JSON.
Проєкт:
MovieSearch.Domain
Містить бізнес-модель та основні контракти.
Domain
├── Entities
│ └── Movie.cs
└── Interfaces
├── IRepository.cs
└── IUnitOfWork.cs
Movie не повинен залежати від:
- ASP.NET Core;
- Entity Framework Core;
- SQL Server;
- HTTP;
- React.
Приклад:
public class Movie
{
public int Id { get; set; }
public string Title { get; set; } = string.Empty;
public string Director { get; set; } = string.Empty;
public string Genre { get; set; } = string.Empty;
public int ReleaseYear { get; set; }
public string? PosterPath { get; set; }
public string? Description { get; set; }
}Проєкт:
MovieSearch.Application
Містить прикладну логіку та use cases.
Application
├── DTOs
│ ├── MovieDto.cs
│ ├── MovieCreateDto.cs
│ └── MovieUpdateDto.cs
├── Interfaces
│ ├── IMovieService.cs
│ └── IFileService.cs
├── Services
│ └── MovieService.cs
└── Exceptions
Application:
- не використовує
DbContext; - не використовує Controller;
- не використовує
HttpContext; - не використовує
IFormFile; - не залежить від React.
Основний контракт:
public interface IMovieService
{
Task<IEnumerable<MovieDto>> GetAllAsync();
Task<MovieDto?> GetByIdAsync(int id);
Task<int> CreateAsync(MovieCreateDto dto);
Task<bool> UpdateAsync(int id, MovieUpdateDto dto);
Task<bool> DeleteAsync(int id);
}MovieService працює через абстракції:
MovieService
↓
IUnitOfWork
↓
IRepository<Movie>
а не безпосередньо через MovieDbContext.
DTO використовуються як контракти між Web API та Application.
Створити:
MovieDto
MovieCreateDto
MovieUpdateDto
Не передавати Entity Movie безпосередньо як API-модель.
Схема:
React
↓ JSON
WebApi
↓
DTO
↓
Application
↓
Domain Entity
DTO повинні приховувати внутрішню структуру Domain від клієнта.
Проєкт:
MovieSearch.Infrastructure
Містить технічні реалізації:
Infrastructure
├── Persistence
│ ├── MovieDbContext.cs
│ └── Configurations
├── Repositories
│ ├── Repository.cs
│ └── MovieRepository.cs
├── Services
│ └── FileService.cs
└── DependencyInjection
Тут знаходяться:
- Entity Framework Core;
DbContext;- SQL Server;
- Repository;
- Unit of Work;
- файлове сховище.
Напрямок залежності:
Application
↑
│ implements
Infrastructure
Наприклад:
IUnitOfWork
↑
EFUnitOfWork
IRepository<Movie>
↑
MovieRepository
IFileService
↑
FileService
Проєкт:
MovieSearch.WebApi
Це Presentation Layer backend.
MovieSearch.WebApi
├── Controllers
│ └── MoviesController.cs
├── Middleware
├── Program.cs
└── appsettings.json
Web API відповідає за:
- HTTP;
- routing;
- model binding;
- HTTP status codes;
- authentication / authorization;
- JSON;
- CORS;
- dependency injection.
Контролер повинен бути тонким:
[ApiController]
[Route("api/[controller]")]
public class MoviesController : ControllerBase
{
private readonly IMovieService _movieService;
public MoviesController(IMovieService movieService)
{
_movieService = movieService;
}
[HttpGet]
public async Task<IActionResult> GetAll()
{
var movies = await _movieService.GetAllAsync();
return Ok(movies);
}
}Контролер не повинен містити:
DbContext
SQL-запити
бізнес-правила
роботу з файлами
складну логіку
Program.cs є точкою, де конкретні реалізації підключаються до абстракцій.
Наприклад:
builder.Services.AddApplication();
builder.Services.AddInfrastructure(
builder.Configuration);Логіка:
IMovieService
↓
MovieService
IUnitOfWork
↓
EFUnitOfWork
IFileService
↓
FileService
Таким чином Application знає тільки про контракти, а конкретні реалізації підключаються зовні.
Створити окремий React-проєкт:
movie-search-client
Рекомендована структура:
movie-search-client
├── src
│ ├── components
│ ├── pages
│ ├── services
│ │ └── movieApi.js
│ ├── models
│ ├── App.jsx
│ └── main.jsx
└── package.json
React відповідає за:
- UI;
- маршрутизацію;
- форми;
- клієнтську валідацію;
- відображення помилок;
- роботу з API;
- завантаження постерів.
React не повинен працювати з:
DbContext
Repository
UnitOfWork
SQL Server
Domain Entity
React знає тільки API-контракти.
Основний потік:
React
│
│ GET /api/movies
▼
MoviesController
│
▼
IMovieService
│
▼
MovieService
│
▼
IUnitOfWork
│
▼
Repository
│
▼
DbContext
│
▼
SQL Server
Відповідь:
SQL Server
↓
DbContext
↓
Repository
↓
MovieService
↓
MovieDto
↓
MoviesController
↓ JSON
React
Реалізувати API:
GET /api/movies
GET /api/movies/{id}
POST /api/movies
PUT /api/movies/{id}
DELETE /api/movies/{id}
React повинен використовувати ці endpoints.
Приклад:
fetch("https://localhost:7000/api/movies")або через окремий API service.
Передбачити API-параметри, наприклад:
GET /api/movies?search=matrix
GET /api/movies?genre=Drama
GET /api/movies?sort=year
GET /api/movies?page=2&pageSize=10
Важливо:
React
↓
параметри запиту
Web API
↓
Application
Infrastructure
↓
SQL-запит
Не потрібно завантажувати всю таблицю в React або Application, якщо операцію можна виконати в базі даних.
Для завантаження файлу використати HTTP multipart/form-data.
Потік:
React
↓
FormData
↓
Web API
↓
IFormFile
↓
Application abstraction
↓
IFileService
↓
Infrastructure
↓
File System
IFormFile повинен залишатися на межі Web API.
Він не повинен потрапляти в:
Domain
Application
Infrastructure contracts
Розділити відповідальність:
React
→ клієнтська валідація
Web API
→ HTTP/model validation
Application
→ бізнес-правила
Database
→ constraints
Наприклад:
Title is required
може перевірятися на frontend та API.
А правило:
Не можна створити дубльований фільм
повинно контролюватися Application.
API повинен повертати відповідні статуси:
200 OK
201 Created
204 No Content
400 Bad Request
404 Not Found
409 Conflict
500 Internal Server Error
React повинен коректно обробляти ці відповіді та показувати користувачу зрозуміле повідомлення.
Оскільки React та Web API працюють як окремі застосунки, налаштувати CORS.
Схема:
React
http://localhost:5173
│
│ HTTP
▼
Web API
https://localhost:7000
Дозволити Web API приймати запити від React frontend.
Продемонструвати повний сценарій створення фільму:
React Form
↓
FormData / JSON
↓
POST /api/movies
↓
MoviesController
↓
MovieCreateDto
↓
IMovieService
↓
MovieService
↓
IUnitOfWork
↓
Repository
↓
DbContext
↓
SQL Server
Після успішного створення:
SQL Server
↓
Web API
↓
HTTP 201
↓
React
↓
оновлення UI
- Domain Entity
Movie; - Repository;
- Unit of Work;
- DTO;
- Application Service;
- бізнес-правила;
- EF Core;
- SQL Server;
- Web API;
- CRUD endpoints;
- пошук;
- фільтрація;
- сортування;
- пагінація;
- завантаження постерів;
- DI;
- CORS;
- обробка помилок.
- React;
- список фільмів;
- детальна сторінка;
- Create;
- Edit;
- Delete;
- пошук;
- фільтрація;
- сортування;
- пагінація;
- завантаження постера;
- обробка помилок API.
Після завершення роботи перевірити:
Domain → Application ❌
Domain → Infrastructure ❌
Domain → WebApi ❌
Domain → React ❌
Application → Domain ✓
Application → Infrastructure ❌
Application → WebApi ❌
Infrastructure → Domain ✓
Infrastructure → Application ✓
WebApi → Application ✓
WebApi → Infrastructure ✓
Для WebApi → Infrastructure важливо: ця залежність потрібна для Composition Root та реєстрації конкретних реалізацій.
React:
React → Web API через HTTP ✓
React → .NET Project Reference ❌
Показати:
- структуру Solution;
- Project References;
- структуру Domain;
- структуру Application;
- структуру Infrastructure;
- структуру Web API;
- структуру React;
Program.csта Dependency Injection;MovieService;- Repository / Unit of Work;
- API endpoints;
- React API service;
- повний HTTP-запит від React до SQL Server.
Окремо пояснити:
Чому React не має доступу до Domain, Repository або DbContext?
Чому Application не знає про Entity Framework Core?
Чому Infrastructure залежить від внутрішніх шарів?
Де знаходиться Composition Root?
Де знаходиться бізнес-логіка?
Недостатньо просто створити декілька папок.
Правильна структура повинна забезпечувати:
React
↓ HTTP
Web API
↓
Application
↓
Domain
↑
Infrastructure
При цьому:
Domain
не знає про EF Core
Application
не знає про ASP.NET Core
Application
не знає про SQL Server
Application
не знає про React
React
не знає про структуру backend
Infrastructure
реалізує контракти внутрішніх шарів
Головна мета:
Заміна SQL Server, Entity Framework Core, файлового сховища або React frontend не повинна вимагати переписування основної бізнес-логіки Application та Domain.
Саме правильні межі та зв'язки між проєктами, а не кількість папок, є головним критерієм виконання роботи.