Application Layer Overview

Application services are the orchestrators of business logic in the Shirinzad platform. They sit between the presentation layer (API controllers) and the domain layer (entities and domain services), coordinating use cases and managing transactions.

Architecture: All services inherit from ABP's ApplicationService base class and implement interface contracts defined in the Application.Contracts project.

Key Responsibilities

  • Orchestrate domain logic and business workflows
  • Handle DTO mapping between entities and API responses
  • Manage transactions and unit of work
  • Implement authorization and permission checks
  • Integrate with caching, logging, and cross-cutting concerns
  • Validate input and handle exceptions gracefully

Service Statistics

32+
Application Services
120+
DTOs (Input/Output)
150+
Service Methods
10+
AutoMapper Profiles

Service Architecture

Service Flow Diagram

graph TB subgraph API["API Layer"] Controller[API Controller] end subgraph AppService["Application Service Layer"] Service[Application Service] Validation[Input Validation] Auth[Authorization Check] Mapping[DTO Mapping] Cache[Cache Management] end subgraph Domain["Domain Layer"] DomainService[Domain Service] Entity[Domain Entity] Repository[Repository] end subgraph Infrastructure["Infrastructure"] DB[(Database)] Redis[(Redis Cache)] end Controller -->|1. HTTP Request| Service Service -->|2. Validate Input| Validation Service -->|3. Check Permissions| Auth Service -->|4. Get/Create Entity| Repository Repository -->|5. Query/Update| DB Service -->|6. Business Logic| DomainService DomainService -->|7. Execute Rules| Entity Service -->|8. Check Cache| Cache Cache -.->|Cache Hit/Miss| Redis Service -->|9. Map to DTO| Mapping Service -->|10. Return DTO| Controller

Services by Category

Catalog Services (7 Services)

Manage products, categories, brands, tags, reviews, wishlists, and product comparison.

Service Key Methods Description
ProductAppService GetAsync(id)
GetListAsync(input)
GetBySlugAsync(slug)
CreateAsync(input)
UpdateAsync(id, input)
DeleteAsync(id)
GetFeaturedAsync()
IncrementViewCountAsync(id)
Complete product CRUD with caching, view tracking, featured products, slug-based lookup
CategoryAppService GetAsync(id)
GetListAsync(input)
GetTreeAsync()
CreateAsync(input)
UpdateAsync(id, input)
DeleteAsync(id)
Hierarchical category management with parent-child relationships, tree structure retrieval
BrandAppService GetAsync(id)
GetListAsync(input)
CreateAsync(input)
UpdateAsync(id, input)
DeleteAsync(id)
Brand/manufacturer management
TagAppService GetAsync(id)
GetListAsync(input)
CreateAsync(input)
UpdateAsync(id, input)
DeleteAsync(id)
GetPopularAsync(count)
Tag management with popular tags retrieval
ProductReviewAppService GetListByProductAsync(productId)
CreateAsync(input)
ApproveAsync(id)
RejectAsync(id)
AddReplyAsync(reviewId, input)
LikeReviewAsync(reviewId)
Product reviews with moderation, replies, likes, image uploads
WishlistAppService GetMyWishlistsAsync()
GetDefaultWishlistAsync()
CreateWishlistAsync(input)
AddItemAsync(input)
RemoveItemAsync(itemId)
MoveItemAsync(itemId, targetWishlistId)
Multiple wishlists per user, default wishlist, item management
ComparisonAppService GetMyComparisonListAsync()
AddProductAsync(productId)
RemoveProductAsync(productId)
ClearAsync()
CompareProductsAsync(productIds)
Product comparison feature, side-by-side specification comparison
ProductSearchAppService SearchAsync(query)
AdvancedSearchAsync(filter)
GetSuggestionsAsync(term)
FilterByCategoryAsync(categoryId, filter)
Advanced product search with filters, auto-suggestions, category filtering

Order Services (5 Services)

Shopping cart, checkout, order management, payments, and shipments.

Service Key Methods Description
CartAppService GetMyCartAsync()
AddItemAsync(input)
UpdateQuantityAsync(itemId, quantity)
RemoveItemAsync(itemId)
ClearCartAsync()
ApplyDiscountCodeAsync(code)
CalculateTotalsAsync()
Shopping cart management for guests and registered users, discount codes, total calculation
OrderAppService GetAsync(id)
GetByOrderNumberAsync(orderNumber)
GetUserOrdersAsync(input)
CreateFromCartAsync(input)
UpdateStatusAsync(id, status)
CancelOrderAsync(id, reason)
GetOrderStatisticsAsync()
Complete order lifecycle management, status updates, order history, cancellations
DiscountAppService GetAsync(id)
GetListAsync(input)
CreateAsync(input)
UpdateAsync(id, input)
DeleteAsync(id)
ValidateDiscountCodeAsync(code)
CalculateDiscountAsync(code, subtotal)
Discount and promo code management, validation, calculation
PaymentAppService InitiatePaymentAsync(orderId, methodId)
VerifyPaymentAsync(transactionId)
GetPaymentStatusAsync(paymentId)
RequestRefundAsync(paymentId, reason)
GetPaymentMethodsAsync()
Payment gateway integration, transaction verification, refund processing
ShipmentAppService CreateShipmentAsync(orderId, input)
GetShipmentAsync(id)
UpdateStatusAsync(id, status)
AssignTrackingNumberAsync(id, tracking)
TrackShipmentAsync(trackingNumber)
GetShippingMethodsAsync()
Shipment creation, tracking, status updates, carrier integration

User & Identity Services (3 Services)

User profiles, addresses, and authentication tracking.

Service Key Methods Description
UserProfileAppService GetMyProfileAsync()
UpdateMyProfileAsync(input)
UploadAvatarAsync(file)
ChangePasswordAsync(input)
GetProfileStatisticsAsync()
User profile management, avatar uploads, password changes, user statistics
UserAddressAppService GetMyAddressesAsync()
GetDefaultAddressAsync()
CreateAsync(input)
UpdateAsync(id, input)
DeleteAsync(id)
SetDefaultAsync(id)
Manage user shipping/billing addresses, default address selection
AccountLockoutService RecordLoginAttemptAsync(userId, isSuccessful)
IsAccountLockedAsync(userId)
GetFailedAttemptsAsync(userId)
UnlockAccountAsync(userId)
Track login attempts, account lockout after failed attempts, security monitoring

Notification Services (2 Services)

In-app notifications, email, SMS, and user preferences.

Service Key Methods Description
NotificationAppService GetMyNotificationsAsync(input)
GetUnreadCountAsync()
MarkAsReadAsync(id)
MarkAllAsReadAsync()
DeleteAsync(id)
SendNotificationAsync(input)
User notifications (in-app), read/unread status, notification management
EmailNotificationService SendOrderConfirmationAsync(orderId)
SendShippingNotificationAsync(shipmentId)
SendPaymentReceiptAsync(paymentId)
SendWelcomeEmailAsync(userId)
SendPasswordResetAsync(userId, token)
Transactional emails using templates, order confirmations, shipping updates

Infrastructure Services (8 Services)

Caching, file storage, auditing, reporting, and system health.

Service Key Methods Description
CacheService GetAsync(key)
GetOrCreateAsync(key, factory, expiration)
SetAsync(key, value, expiration)
RemoveAsync(key)
RemoveByPrefixAsync(prefix)
Redis distributed caching abstraction with TTL management
CacheInvalidationService InvalidateProductCacheAsync(productId)
InvalidateCategoryCacheAsync(categoryId)
InvalidateUserCacheAsync(userId)
InvalidateAllCacheAsync()
Smart cache invalidation based on entity changes
CacheWarmingService WarmFeaturedProductsAsync()
WarmCategoriesAsync()
WarmPopularProductsAsync()
WarmAllAsync()
Preload frequently accessed data into cache on startup
FileStorageAppService UploadAsync(file, category)
UploadMultipleAsync(files, category)
GetAsync(id)
DeleteAsync(id)
GenerateThumbnailAsync(fileId)
File upload management, thumbnail generation, local/cloud storage support
AuditLogAppService GetListAsync(filter)
GetByUserAsync(userId, input)
GetByEntityAsync(entityType, entityId)
LogActionAsync(actionType, entityType, entityId)
Comprehensive audit trail, user actions, entity changes tracking
DashboardAppService GetStatisticsAsync()
GetRevenueChartAsync(days)
GetTopProductsAsync(count)
GetRecentOrdersAsync(count)
GetCustomerInsightsAsync()
Admin dashboard statistics, charts, insights, KPIs
ReportAppService GenerateSalesReportAsync(filter)
GenerateProductReportAsync(filter)
GenerateCustomerReportAsync(filter)
ExportToCsvAsync(reportType, filter)
ExportToExcelAsync(reportType, filter)
Business reports generation, export to CSV/Excel
ShopHealthCheckService CheckDatabaseAsync()
CheckRedisAsync()
CheckExternalServicesAsync()
GetSystemStatusAsync()
System health monitoring, database/cache/API availability checks

Security & Validation Services (3 Services)

Rate limiting, input validation, and security monitoring.

Service Key Methods Description
RateLimitService CheckRateLimitAsync(key, maxRequests, window)
IncrementCounterAsync(key)
GetRemainingRequestsAsync(key)
ResetLimitAsync(key)
API rate limiting, DDoS protection, request throttling
FileValidationService ValidateImageAsync(file)
ValidateFileSizeAsync(file, maxSize)
ValidateFileTypeAsync(file, allowedTypes)
ScanForMalwareAsync(file)
File upload validation, size limits, type restrictions, security scanning
GoogleRecaptchaService VerifyAsync(token)
ValidateScoreAsync(token, minScore)
GetCaptchaScoreAsync(token)
Google reCAPTCHA v3 integration for bot protection

Configuration Service (1 Service)

Service Key Methods Description
ShirinzadConfigAppService GetConfigAsync()
UpdateConfigAsync(input)
GetPublicConfigAsync()
UpdateLogoAsync(file)
UpdateFaviconAsync(file)
Site-wide configuration management, contact info, social media, SEO settings

DTO Mapping with AutoMapper

AutoMapper Configuration

All services use AutoMapper for entity-to-DTO mapping. Mapping profiles are defined in the Application project:

// Example AutoMapper Profile
public class ShopApplicationAutoMapperProfile : Profile
{
    public ShopApplicationAutoMapperProfile()
    {
        // Product mappings
        CreateMap<Product, ProductDto>()
            .ForMember(d => d.BrandName, opt => opt.MapFrom(s => s.Brand.Name))
            .ForMember(d => d.CategoryIds, opt => opt.Ignore())
            .ForMember(d => d.CategoryNames, opt => opt.Ignore());

        CreateMap<CreateProductDto, Product>()
            .ForMember(d => d.Id, opt => opt.Ignore());

        CreateMap<UpdateProductDto, Product>()
            .ForMember(d => d.Id, opt => opt.Ignore());

        // Order mappings
        CreateMap<Order, OrderDto>()
            .ForMember(d => d.Items, opt => opt.MapFrom(s => s.Items));

        CreateMap<OrderItem, OrderItemDto>();

        CreateMap<CreateOrderDto, Order>()
            .ForMember(d => d.Id, opt => opt.Ignore())
            .ForMember(d => d.OrderNumber, opt => opt.Ignore());
    }
}

Common DTO Patterns

DTO Type Purpose Example
EntityDto Output - return entity data ProductDto, OrderDto
CreateDto Input - create new entity CreateProductDto, CreateOrderDto
UpdateDto Input - update existing entity UpdateProductDto, UpdateOrderDto
ListDto Output - simplified list items ProductListDto, OrderSummaryDto
FilterDto Input - search/filter parameters ProductFilterDto, OrderFilterDto

Service Implementation Examples

Example 1: ProductAppService - Get with Caching

public class ProductAppService : ApplicationService, IProductAppService
{
    private readonly IRepository<Product, Guid> _productRepository;
    private readonly IRepository<Brand, Guid> _brandRepository;
    private readonly CacheService _cacheService;

    public async Task<ProductDto> GetAsync(Guid id)
    {
        // Try cache first (2-hour TTL)
        var cacheKey = CacheKeys.GetProductDetailsKey(id);
        var cachedDto = await _cacheService.GetOrCreateAsync(
            cacheKey,
            async () => await GetProductDtoAsync(id),
            TimeSpan.FromHours(2)
        );

        return cachedDto;
    }

    private async Task<ProductDto> GetProductDtoAsync(Guid id)
    {
        var product = await _productRepository.GetAsync(id);
        var dto = ObjectMapper.Map<Product, ProductDto>(product);

        // Load related data
        if (product.BrandId.HasValue)
        {
            var brand = await _brandRepository.FindAsync(product.BrandId.Value);
            dto.BrandName = brand?.Name;
        }

        return dto;
    }

    public async Task<ProductDto> CreateAsync(CreateProductDto input)
    {
        // Validate slug uniqueness
        var existingProduct = await _productRepository
            .FirstOrDefaultAsync(x => x.Slug == input.Slug);

        if (existingProduct != null)
        {
            throw new UserFriendlyException("این نام مستعار قبلاً استفاده شده است");
        }

        // Create entity
        var product = new Product(GuidGenerator.Create(), input.Name, input.Slug);
        product.Price = input.Price;
        product.Description = input.Description;
        // ... set other properties

        await _productRepository.InsertAsync(product);

        // Invalidate related caches
        await _cacheInvalidationService.InvalidateProductCacheAsync(product.Id);

        return ObjectMapper.Map<Product, ProductDto>(product);
    }
}

Example 2: OrderAppService - Create from Cart

public class OrderAppService : ApplicationService, IOrderAppService
{
    private readonly IRepository<Order, Guid> _orderRepository;
    private readonly IRepository<Cart, Guid> _cartRepository;
    private readonly IRepository<Product, Guid> _productRepository;
    private readonly ICurrentUser _currentUser;

    public async Task<OrderDto> CreateFromCartAsync(CreateOrderFromCartDto input)
    {
        // Authorize
        if (!_currentUser.IsAuthenticated)
        {
            throw new UserFriendlyException("لطفاً وارد حساب کاربری خود شوید");
        }

        // Get user's cart
        var cart = await _cartRepository
            .Include(c => c.Items)
            .FirstOrDefaultAsync(c => c.UserId == _currentUser.Id && c.IsActive);

        if (cart == null || !cart.Items.Any())
        {
            throw new UserFriendlyException("سبد خرید شما خالی است");
        }

        // Validate stock availability
        foreach (var item in cart.Items)
        {
            var product = await _productRepository.GetAsync(item.ProductId);
            if (product.StockQuantity < item.Quantity)
            {
                throw new UserFriendlyException($"محصول {product.Name} موجود نیست");
            }
        }

        // Create order
        var orderNumber = await GenerateOrderNumberAsync();
        var order = new Order(GuidGenerator.Create(), orderNumber);
        order.UserId = _currentUser.Id;
        order.CustomerName = input.CustomerName;
        order.CustomerEmail = input.CustomerEmail;
        order.CustomerPhone = input.CustomerPhone;
        order.ShippingAddress = input.ShippingAddress;
        order.Subtotal = cart.Subtotal;
        order.Total = cart.Total;
        order.Status = OrderStatus.Pending;

        // Add order items from cart
        foreach (var cartItem in cart.Items)
        {
            var orderItem = new OrderItem(GuidGenerator.Create());
            orderItem.OrderId = order.Id;
            orderItem.ProductId = cartItem.ProductId;
            orderItem.ProductName = cartItem.ProductName;
            orderItem.Quantity = cartItem.Quantity;
            orderItem.UnitPrice = cartItem.Price;
            orderItem.LineTotal = cartItem.Quantity * cartItem.Price;

            order.Items.Add(orderItem);

            // Decrease product stock
            var product = await _productRepository.GetAsync(cartItem.ProductId);
            product.DecreaseStock(cartItem.Quantity);
            await _productRepository.UpdateAsync(product);
        }

        await _orderRepository.InsertAsync(order);

        // Clear cart
        cart.IsActive = false;
        await _cartRepository.UpdateAsync(cart);

        // Send notification
        await _notificationService.SendOrderConfirmationAsync(order.Id);

        return ObjectMapper.Map<Order, OrderDto>(order);
    }

    private async Task<string> GenerateOrderNumberAsync()
    {
        // Generate unique order number: ORD-20251004-0001
        var date = DateTime.Now.ToString("yyyyMMdd");
        var count = await _orderRepository
            .CountAsync(o => o.OrderNumber.StartsWith($"ORD-{date}"));

        return $"ORD-{date}-{(count + 1):D4}";
    }
}

Example 3: CartAppService - Apply Discount

public async Task<CartDto> ApplyDiscountCodeAsync(string code)
{
    // Get user's cart
    var cart = await GetOrCreateCartAsync();

    // Validate discount code
    var discount = await _discountRepository
        .FirstOrDefaultAsync(d => d.Code == code && d.IsActive);

    if (discount == null)
    {
        throw new UserFriendlyException("کد تخفیف معتبر نیست");
    }

    // Check date range
    if (discount.StartDate > DateTime.Now || discount.EndDate < DateTime.Now)
    {
        throw new UserFriendlyException("کد تخفیف منقضی شده است");
    }

    // Check minimum order amount
    if (discount.MinimumOrderAmount.HasValue &&
        cart.Subtotal < discount.MinimumOrderAmount.Value)
    {
        throw new UserFriendlyException(
            $"حداقل مبلغ سفارش برای این کد تخفیف {discount.MinimumOrderAmount:N0} تومان است"
        );
    }

    // Calculate discount amount
    decimal discountAmount = 0;
    if (discount.DiscountType == DiscountType.Percentage)
    {
        discountAmount = cart.Subtotal * (discount.DiscountValue / 100);
        if (discount.MaximumDiscountAmount.HasValue)
        {
            discountAmount = Math.Min(discountAmount, discount.MaximumDiscountAmount.Value);
        }
    }
    else
    {
        discountAmount = discount.DiscountValue;
    }

    // Apply discount
    cart.DiscountCode = code;
    cart.DiscountAmount = discountAmount;
    cart.Total = cart.Subtotal - discountAmount;

    await _cartRepository.UpdateAsync(cart);

    // Invalidate cache
    await _cacheInvalidationService.InvalidateUserCacheAsync(_currentUser.Id.Value);

    return ObjectMapper.Map<Cart, CartDto>(cart);
}

Service Development Best Practices

Key Guidelines

Do's

  • Always use async/await for database operations
  • Use DTOs for all inputs and outputs
  • Implement proper authorization checks
  • Validate inputs before processing
  • Use caching for frequently accessed data
  • Invalidate cache when data changes
  • Log important operations and errors
  • Return user-friendly error messages (Persian)
  • Use transactions for multi-step operations
  • Follow single responsibility principle

Don'ts

  • Don't expose entities directly to API
  • Don't perform business logic in controllers
  • Don't use synchronous database calls
  • Don't forget to check permissions
  • Don't return generic error messages
  • Don't perform N+1 queries (use Include)
  • Don't cache user-specific data globally
  • Don't bypass validation for "convenience"
  • Don't ignore exception handling
  • Don't create god services (keep focused)

Performance Optimization

Technique Implementation Benefit
Caching Use CacheService.GetOrCreateAsync() for expensive queries 80-95% response time reduction
Projections Use Select() to fetch only needed columns 40-60% memory reduction
AsNoTracking Add .AsNoTracking() for read-only queries 20-30% faster queries
Eager Loading Use Include() to avoid N+1 queries Prevent multiple round-trips to database
Pagination Always use Skip()/Take() for lists Prevent loading thousands of records
Batch Operations Use InsertManyAsync() for bulk inserts 10x faster than individual inserts

Related Documentation