= Напреден развој на апликација = == Pooling == Во backend слојот на !StockMaster апликацијата користиме ASP.NET Core со Entity Framework Core за комуникација со PostgreSQL базата на податоци. Поради оваа архитектура, конекциите со базата не се креираат рачно во кодот. Наместо тоа, тие автоматски се управуваат од Npgsql, кој е стандардниот .NET data provider за PostgreSQL. Connection pooling е вградена функционалност на Npgsql и е активирана по default. '''appsettings.json''' {{{ { "Logging": { "LogLevel": { "Default": "Information", "Microsoft.AspNetCore": "Warning" } }, "AllowedHosts": "*", "ConnectionStrings": { "DefaultConnection": "Host=localhost;Database=StockDb;Username=YOUR_USERNAME;Password=YOUR_PASSWORD;Pooling=true;MinPoolSize=5;MaxPoolSize=100;Connection Idle Lifetime=300;" } } }}} '''Pooling=true''' Овозможува connection pooling, што значи дека базата ги реупотребува постоечките конекции наместо да креира нова конекција за секое барање. '''!MinPoolSize=5''' Го дефинира минималниот број на активни конекции што се одржуваат во connection pool при стартување на апликацијата. '''!MaxPoolSize=100''' Го ограничува максималниот број на истовремени конекции дозволени во connection pool при зголемен traffic. '''Connection Idle Lifetime=300''' Го одредува времето (во секунди) колку една неактивна конекција останува во pool пред автоматски да биде затворена и отстранета ако не се користи. == Трансакции == === Креирање на продажба (!SaleService.cs) === Зошто Transaction? Кога се креира продажен запис, количината мора да се намали од залихата. Ако настане грешка при намалувањето на залихата, тогаш и продажниот запис не треба да се зачува, за да се обезбеди податоците да останат конзистентни. {{{ public async Task CreateSaleAsync(Sale sale) { using var transaction = await _context.Database.BeginTransactionAsync(); try { _context.Sales.Add(sale); await _context.SaveChangesAsync(); foreach (var item in sale.SaleItems) { var stock = await _context.WarehouseStocks .FirstOrDefaultAsync(ws => ws.WarehouseId == sale.WarehouseId && ws.ProductId == item.ProductId); if (stock == null || stock.QuantityOnHand < item.Quantity) { throw new Exception("Insufficient Stock"); } stock.QuantityOnHand -= item.Quantity; stock.LastUpdated = DateTime.Now; } await _context.SaveChangesAsync(); await transaction.CommitAsync(); return true; } catch { await transaction.RollbackAsync(); return false; } } }}} === Примање на нарачка (!PurchaseOrderService.cs) === Зошто Transaction? Кога статусот на нарачката се менува во „Received“, залихите мора да се зголемат. Ако едната операција се изврши, а другата не, ќе настане неконзистентност во податоците. {{{ public async Task ReceivePurchaseOrderAsync(int poId) { using var transaction = await _context.Database.BeginTransactionAsync(); try { var po = await GetPurchaseOrderByIdAsync(poId); if (po == null || po.Status == "Received") return false; foreach (var item in po.PurchaseOrderItems) { var stock = await _context.WarehouseStocks .FirstOrDefaultAsync(ws => ws.WarehouseId == po.WarehouseId && ws.ProductId == item.ProductId); if (stock == null) { stock = new WarehouseStock { WarehouseId = po.WarehouseId, ProductId = item.ProductId, QuantityOnHand = item.Quantity }; _context.WarehouseStocks.Add(stock); } else { stock.QuantityOnHand += item.Quantity; } stock.LastUpdated = DateTime.Now; item.ReceivedQuantity = item.Quantity; } po.Status = "Received"; po.ActualDeliveryDate = DateTime.Now.Date; await _context.SaveChangesAsync(); await transaction.CommitAsync(); return true; } catch { await transaction.RollbackAsync(); return false; } } }}} === Креирање на нарачка (!PurchaseOrderService.cs) === Зошто Transaction? Насловот на нарачката и ставките (Items) претставуваат една целина. Ако едното се зачува, а другото не, ќе се создаде празна нарачка и податоците ќе бидат неконзистентни. {{{ public async Task CreatePurchaseOrderAsync(PurchaseOrder po) { using var transaction = await _context.Database.BeginTransactionAsync(); try { _context.PurchaseOrders.Add(po); await _context.SaveChangesAsync(); await transaction.CommitAsync(); return true; } catch { await transaction.RollbackAsync(); return false; } } }}} === Креирање на корисник (!AuthService.cs) === Зошто Transaction? При креирање на корисник, за да се обезбеди целосност при хеширање на лозинка и при доделување улоги, како и за идни дополнителни операции. {{{ public async Task CreateUserAsync(User user, string password) { using var transaction = await _context.Database.BeginTransactionAsync(); try { user.Password = BCrypt.Net.BCrypt.HashPassword(password); _context.Users.Add(user); await _context.SaveChangesAsync(); await transaction.CommitAsync(); return true; } catch { await transaction.RollbackAsync(); return false; } } }}} === Бришење на производ (!ProductService.cs) === Зошто Transaction? Се користи Soft Delete (поставување на производот како неактивен). Ова е критична промена на податоци и ако врската со базата се прекине за време на процесот, состојбата на производот не смее да остане недефинирана. {{{ public async Task DeleteProductAsync(int id) { using var transaction = await _context.Database.BeginTransactionAsync(); try { var product = await _context.Products.FindAsync(id); if (product != null) { product.IsActive = false; await _context.SaveChangesAsync(); await transaction.CommitAsync(); return true; } return false; } catch { await transaction.RollbackAsync(); return false; } } }}}