Skip to content

Instantly share code, notes, and snippets.

@sunmeat
Last active September 14, 2026 13:06
Show Gist options
  • Select an option

  • Save sunmeat/00a35de516f8b19af46d15cd231b5e5c to your computer and use it in GitHub Desktop.

Select an option

Save sunmeat/00a35de516f8b19af46d15cd231b5e5c to your computer and use it in GitHub Desktop.
ДЗ: чиста архітектура

Clean Architecture + ASP.NET Core Web API + React

Розробка веб-додатку «Кінопошук» / «10 найкращих фільмів»

1. Мета

Перебудувати попередній проєкт «Кінопошук» у 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 та правильні залежності між проєктами.


2. Структура Solution

Створити приблизно таку структуру:

MovieSearch.sln
│
├── MovieSearch.Domain
│
├── MovieSearch.Application
│
├── MovieSearch.Infrastructure
│
├── MovieSearch.WebApi
│
└── movie-search-client

Останній проєкт є окремим React-проєктом і не має Project Reference на .NET-проєкти.


3. Зв'язки між проєктами

Головна частина завдання — правильно налаштувати залежності.

                    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.


4. Domain

Проєкт:

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; }
}

5. Application

Проєкт:

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.


6. DTO

DTO використовуються як контракти між Web API та Application.

Створити:

MovieDto
MovieCreateDto
MovieUpdateDto

Не передавати Entity Movie безпосередньо як API-модель.

Схема:

React
  ↓ JSON
WebApi
  ↓
DTO
  ↓
Application
  ↓
Domain Entity

DTO повинні приховувати внутрішню структуру Domain від клієнта.


7. Infrastructure

Проєкт:

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

8. Web API

Проєкт:

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-запити
бізнес-правила
роботу з файлами
складну логіку

9. Composition Root

Program.cs є точкою, де конкретні реалізації підключаються до абстракцій.

Наприклад:

builder.Services.AddApplication();

builder.Services.AddInfrastructure(
    builder.Configuration);

Логіка:

IMovieService
      ↓
MovieService

IUnitOfWork
      ↓
EFUnitOfWork

IFileService
      ↓
FileService

Таким чином Application знає тільки про контракти, а конкретні реалізації підключаються зовні.


10. React Frontend

Створити окремий 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-контракти.


11. Взаємодія React та Web API

Основний потік:

React
   │
   │ GET /api/movies
   ▼
MoviesController
   │
   ▼
IMovieService
   │
   ▼
MovieService
   │
   ▼
IUnitOfWork
   │
   ▼
Repository
   │
   ▼
DbContext
   │
   ▼
SQL Server

Відповідь:

SQL Server
   ↓
DbContext
   ↓
Repository
   ↓
MovieService
   ↓
MovieDto
   ↓
MoviesController
   ↓ JSON
React

12. CRUD через API

Реалізувати 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.


13. Пошук, фільтрація та пагінація

Передбачити 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, якщо операцію можна виконати в базі даних.


14. Робота з постерами

Для завантаження файлу використати HTTP multipart/form-data.

Потік:

React
   ↓
FormData
   ↓
Web API
   ↓
IFormFile
   ↓
Application abstraction
   ↓
IFileService
   ↓
Infrastructure
   ↓
File System

IFormFile повинен залишатися на межі Web API.

Він не повинен потрапляти в:

Domain
Application
Infrastructure contracts

15. Валідація

Розділити відповідальність:

React
  → клієнтська валідація

Web API
  → HTTP/model validation

Application
  → бізнес-правила

Database
  → constraints

Наприклад:

Title is required

може перевірятися на frontend та API.

А правило:

Не можна створити дубльований фільм

повинно контролюватися Application.


16. HTTP status codes

API повинен повертати відповідні статуси:

200 OK
201 Created
204 No Content
400 Bad Request
404 Not Found
409 Conflict
500 Internal Server Error

React повинен коректно обробляти ці відповіді та показувати користувачу зрозуміле повідомлення.


17. CORS

Оскільки React та Web API працюють як окремі застосунки, налаштувати CORS.

Схема:

React
http://localhost:5173
        │
        │ HTTP
        ▼
Web API
https://localhost:7000

Дозволити Web API приймати запити від React frontend.


18. Основний CRUD-сценарій

Продемонструвати повний сценарій створення фільму:

React Form
    ↓
FormData / JSON
    ↓
POST /api/movies
    ↓
MoviesController
    ↓
MovieCreateDto
    ↓
IMovieService
    ↓
MovieService
    ↓
IUnitOfWork
    ↓
Repository
    ↓
DbContext
    ↓
SQL Server

Після успішного створення:

SQL Server
    ↓
Web API
    ↓
HTTP 201
    ↓
React
    ↓
оновлення UI

19. Що потрібно реалізувати

Backend

  • Domain Entity Movie;
  • Repository;
  • Unit of Work;
  • DTO;
  • Application Service;
  • бізнес-правила;
  • EF Core;
  • SQL Server;
  • Web API;
  • CRUD endpoints;
  • пошук;
  • фільтрація;
  • сортування;
  • пагінація;
  • завантаження постерів;
  • DI;
  • CORS;
  • обробка помилок.

Frontend

  • React;
  • список фільмів;
  • детальна сторінка;
  • Create;
  • Edit;
  • Delete;
  • пошук;
  • фільтрація;
  • сортування;
  • пагінація;
  • завантаження постера;
  • обробка помилок API.

20. Архітектурна перевірка

Після завершення роботи перевірити:

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 ❌

21. Що продемонструвати на захисті

Показати:

  1. структуру Solution;
  2. Project References;
  3. структуру Domain;
  4. структуру Application;
  5. структуру Infrastructure;
  6. структуру Web API;
  7. структуру React;
  8. Program.cs та Dependency Injection;
  9. MovieService;
  10. Repository / Unit of Work;
  11. API endpoints;
  12. React API service;
  13. повний HTTP-запит від React до SQL Server.

Окремо пояснити:

Чому React не має доступу до Domain, Repository або DbContext?

Чому Application не знає про Entity Framework Core?

Чому Infrastructure залежить від внутрішніх шарів?

Де знаходиться Composition Root?

Де знаходиться бізнес-логіка?


22. Критерій правильної архітектури

Недостатньо просто створити декілька папок.

Правильна структура повинна забезпечувати:

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.

Саме правильні межі та зв'язки між проєктами, а не кількість папок, є головним критерієм виконання роботи.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment