Ответ
Распространенный подход — использование шаблона Repository для базового CRUD и выделение Service Layer для сложной бизнес-логики. CRUD-операции могут быть сгенерированы инструментами (например, шаблонами Scaffold в ASP.NET Core).
1. Уровень данных (Repository / Generic Repository) - Сгенерированный/стандартный CRUD:
// Обобщенный репозиторий (может быть сгенерирован)
public interface IRepository<T> where T : class
{
Task<T?> GetByIdAsync(int id);
Task<IEnumerable<T>> GetAllAsync();
Task AddAsync(T entity);
void Update(T entity); // Обычно не async, т.к. только меняет состояние в контексте
Task DeleteAsync(int id);
// ... другие базовые методы
}
public class EfRepository<T> : IRepository<T> where T : class
{
private readonly AppDbContext _context;
private readonly DbSet<T> _dbSet;
public EfRepository(AppDbContext context)
{
_context = context;
_dbSet = context.Set<T>();
}
public async Task<T?> GetByIdAsync(int id) => await _dbSet.FindAsync(id);
public async Task<IEnumerable<T>> GetAllAsync() => await _dbSet.ToListAsync();
public async Task AddAsync(T entity) => await _dbSet.AddAsync(entity);
public void Update(T entity) => _dbSet.Update(entity);
public async Task DeleteAsync(int id)
{
var entity = await GetByIdAsync(id);
if (entity != null) _dbSet.Remove(entity);
}
// SaveChanges обычно вызывается на уровне Unit of Work или сервиса
}
2. Уровень бизнес-логики (Service) - Кастомная логика:
// Сервис, который использует репозиторий и добавляет бизнес-правила
public class ProductService
{
private readonly IRepository<Product> _productRepository;
private readonly IEmailService _emailService; // Пример другой зависимости
public ProductService(IRepository<Product> productRepository, IEmailService emailService)
{
_productRepository = productRepository;
_emailService = emailService;
}
// Кастомный метод: получение товаров со скидкой
public async Task<IEnumerable<Product>> GetDiscountedProductsAsync()
{
var allProducts = await _productRepository.GetAllAsync();
// Бизнес-правило: скидка применяется к товарам старше 30 дней
return allProducts.Where(p => (DateTime.Now - p.CreatedDate).Days > 30)
.Select(p => new Product {
Id = p.Id,
Name = p.Name,
Price = p.Price * 0.8m // 20% скидка
});
}
// Кастомный метод: размещение заказа с проверками и побочными эффектами
public async Task PlaceOrderAsync(int productId, int quantity, string userId)
{
// 1. Использование CRUD: получить товар
var product = await _productRepository.GetByIdAsync(productId);
if (product == null) throw new ArgumentException("Товар не найден.");
if (product.Stock < quantity) throw new InvalidOperationException("Недостаточно товара на складе.");
// 2. Бизнес-логика: обновить остаток
product.Stock -= quantity;
_productRepository.Update(product); // Использование CRUD: обновление
// 3. Создание сложной сущности (Order) - это тоже часть логики
var order = new Order { ProductId = productId, UserId = userId, Quantity = quantity };
// ... сохранение заказа через свой репозиторий IRepository<Order>
// 4. Побочный эффект: уведомление
if (product.Stock < 10) // Бизнес-правило: низкий запас
{
await _emailService.SendLowStockAlertAsync(product.Name, product.Stock);
}
}
}
Итог: CRUD-репозиторий предоставляет простой доступ к данным, а сервисный слой инкапсулирует как эти операции сочетаются для выполнения бизнес-требований, обеспечивая разделение ответственности.
Ответ 18+ 🔞
Ну вот смотри, как это обычно на практике выглядит, блядь. Все эти ваши репозитории и сервисы — это как разграничить, кто за какую хуйню отвечает, чтобы потом не было мучительно больно.
Снизу, на уровне данных, у тебя сидит этот самый Repository. Его задача — тупо долбиться в базу, как дятел. Получи запись, вставь запись, обнови, удали. Никаких мозгов, чистейший CRUD. Его хоть автоматом сгенерировать можно, всем похуй.
// Вот этот чувак — тупой как пробка, но полезный.
public interface IRepository<T> where T : class
{
Task<T?> GetByIdAsync(int id);
Task<IEnumerable<T>> GetAllAsync();
Task AddAsync(T entity);
void Update(T entity);
Task DeleteAsync(int id);
}
А теперь поднимаемся на этаж выше, в Service. Вот тут уже начинается настоящая жизнь, ёпта! Тут тебе и проверки, и бизнес-правила, и рассылки уведомлений — вся эта ебальная логика, из-за которой проект вообще пишется.
public class ProductService
{
private readonly IRepository<Product> _productRepository;
private readonly IEmailService _emailService;
public ProductService(IRepository<Product> productRepository, IEmailService emailService)
{
_productRepository = productRepository;
_emailService = emailService;
}
// Вот смотри, пример: надо товары со скидкой получить.
// Репозиторий бы тупо всё выгрузил, а сервис думает: "Ага, скидка только на старые товары!"
public async Task<IEnumerable<Product>> GetDiscountedProductsAsync()
{
var allProducts = await _productRepository.GetAllAsync();
return allProducts.Where(p => (DateTime.Now - p.CreatedDate).Days > 30)
.Select(p => new Product {
Id = p.Id,
Name = p.Name,
Price = p.Price * 0.8m // Бля, 20% отъём!
});
}
// А вот это уже серьёзнее: оформить заказ.
// Тут одной вставкой в базу не отделаешься, тут целый цирк.
public async Task PlaceOrderAsync(int productId, int quantity, string userId)
{
// 1. Достаём товар через тот самый тупой CRUD
var product = await _productRepository.GetByIdAsync(productId);
if (product == null) throw new ArgumentException("Товар-то не найден, гений!");
if (product.Stock < quantity) throw new InvalidOperationException("На складе пусто, как в твоей голове!");
// 2. Логика: списываем остатки
product.Stock -= quantity;
_productRepository.Update(product); // И обновляем через тот же простой метод
// 3. Создаём заказ — это уже новая сущность, своя история
var order = new Order { ProductId = productId, UserId = userId, Quantity = quantity };
// ... тут бы его тоже через репозиторий заказа сохранить
// 4. А вот и побочный эффект: если запасы на исходе — орем менеджерам
if (product.Stock < 10)
{
await _emailService.SendLowStockAlertAsync(product.Name, product.Stock);
}
}
}
Короче, суть в чём: Repository — это тупой грузчик, который таскает ящики (данные) туда-сюда. А Service — это уже прораб, который решает, какие ящики, куда, в каком порядке таскать, и кому потом позвонить, чтобы объяснить, почему всё так долго. Разделение, блядь, обязанностей, чтобы не получилась одна большая помойка, где всё перемешано.