Repository navigation
Services
Huy Nguyen edited this page Apr 5, 2025
·
2 revisions
This guide will help you understand how to develop services for Mixcore CMS. Services contain the business logic of your application and are typically used by controllers and other services.
Application services orchestrate business processes:
public interface IOrderService
{
Task<OrderResult> PlaceOrderAsync(OrderRequest request);
Task<OrderStatus> GetOrderStatusAsync(int orderId);
Task CancelOrderAsync(int orderId);
}
public class OrderService : IOrderService
{
private readonly IOrderRepository _orderRepository;
private readonly IPaymentService _paymentService;
private readonly IInventoryService _inventoryService;
public OrderService(
IOrderRepository orderRepository,
IPaymentService paymentService,
IInventoryService inventoryService)
{
_orderRepository = orderRepository;
_paymentService = paymentService;
_inventoryService = inventoryService;
}
public async Task<OrderResult> PlaceOrderAsync(OrderRequest request)
{
// 1. Validate inventory
var inventoryStatus = await _inventoryService.CheckAvailabilityAsync(request.Items);
if (!inventoryStatus.IsAvailable)
{
return OrderResult.Failed("Items not available");
}
// 2. Process payment
var paymentResult = await _paymentService.ProcessPaymentAsync(request.Payment);
if (!paymentResult.Success)
{
return OrderResult.Failed("Payment failed");
}
// 3. Create order
var order = new Order
{
Items = request.Items,
PaymentId = paymentResult.PaymentId,
Status = OrderStatus.Pending
};
await _orderRepository.AddAsync(order);
// 4. Update inventory
await _inventoryService.UpdateInventoryAsync(request.Items);
return OrderResult.Success(order.Id);
}
}Domain services contain core business logic:
public interface IDiscountCalculator
{
decimal CalculateDiscount(Order order, Customer customer);
}
public class DiscountCalculator : IDiscountCalculator
{
public decimal CalculateDiscount(Order order, Customer customer)
{
decimal discount = 0;
// Loyalty discount
if (customer.IsLoyaltyMember)
{
discount += order.Total * 0.1m; // 10% discount
}
// Bulk order discount
if (order.Items.Count > 10)
{
discount += order.Total * 0.05m; // 5% discount
}
return discount;
}
}Infrastructure services handle technical concerns:
public interface IEmailService
{
Task SendEmailAsync(string to, string subject, string body);
}
public class SmtpEmailService : IEmailService
{
private readonly SmtpClient _smtpClient;
private readonly ILogger<EmailService> _logger;
public SmtpEmailService(SmtpClient smtpClient, ILogger<EmailService> logger)
{
_smtpClient = smtpClient;
_logger = logger;
}
public async Task SendEmailAsync(string to, string subject, string body)
{
try
{
var message = new MailMessage
{
To = { to },
Subject = subject,
Body = body
};
await _smtpClient.SendMailAsync(message);
_logger.LogInformation("Email sent to {To}", to);
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to send email to {To}", to);
throw;
}
}
}Register services in your module's ConfigureServices method:
public class Module : IModule
{
public void ConfigureServices(IServiceCollection services)
{
// Application services
services.AddScoped<IOrderService, OrderService>();
// Domain services
services.AddSingleton<IDiscountCalculator, DiscountCalculator>();
// Infrastructure services
services.AddSingleton<IEmailService, SmtpEmailService>();
}
}Configure services in appsettings.json:
{
"Email": {
"SmtpServer": "smtp.example.com",
"Port": 587,
"Username": "user@example.com",
"Password": "password"
},
"Order": {
"MaxItems": 100,
"DefaultCurrency": "USD"
}
}Services can depend on other services:
public class OrderProcessingService : IOrderProcessingService
{
private readonly IOrderService _orderService;
private readonly IInventoryService _inventoryService;
private readonly IShippingService _shippingService;
public OrderProcessingService(
IOrderService orderService,
IInventoryService inventoryService,
IShippingService shippingService)
{
_orderService = orderService;
_inventoryService = inventoryService;
_shippingService = shippingService;
}
}Services can have different lifetimes:
public class Module : IModule
{
public void ConfigureServices(IServiceCollection services)
{
// Transient: New instance for each request
services.AddTransient<ITransientService, TransientService>();
// Scoped: Same instance within a request
services.AddScoped<IScopedService, ScopedService>();
// Singleton: Single instance for the application lifetime
services.AddSingleton<ISingletonService, SingletonService>();
}
}Test services using unit tests:
public class OrderServiceTests
{
private readonly Mock<IOrderRepository> _orderRepositoryMock;
private readonly Mock<IPaymentService> _paymentServiceMock;
private readonly Mock<IInventoryService> _inventoryServiceMock;
private readonly OrderService _orderService;
public OrderServiceTests()
{
_orderRepositoryMock = new Mock<IOrderRepository>();
_paymentServiceMock = new Mock<IPaymentService>();
_inventoryServiceMock = new Mock<IInventoryService>();
_orderService = new OrderService(
_orderRepositoryMock.Object,
_paymentServiceMock.Object,
_inventoryServiceMock.Object);
}
[Fact]
public async Task PlaceOrderAsync_SuccessfulOrder_ReturnsSuccess()
{
// Arrange
var request = new OrderRequest
{
Items = new List<OrderItem> { new OrderItem { Id = 1, Quantity = 2 } },
Payment = new PaymentInfo { Amount = 100 }
};
_inventoryServiceMock.Setup(x => x.CheckAvailabilityAsync(It.IsAny<List<OrderItem>>()))
.ReturnsAsync(new InventoryStatus { IsAvailable = true });
_paymentServiceMock.Setup(x => x.ProcessPaymentAsync(It.IsAny<PaymentInfo>()))
.ReturnsAsync(new PaymentResult { Success = true, PaymentId = "123" });
// Act
var result = await _orderService.PlaceOrderAsync(request);
// Assert
Assert.True(result.Success);
_orderRepositoryMock.Verify(x => x.AddAsync(It.IsAny<Order>()), Times.Once);
}
}-
Interface Segregation
- Keep interfaces focused
- Avoid large interfaces
- Use multiple interfaces if needed
-
Dependency Injection
- Use constructor injection
- Avoid service locator pattern
- Keep constructors simple
-
Error Handling
- Use custom exceptions
- Log errors appropriately
- Provide meaningful error messages
-
Performance
- Use async/await
- Implement caching
- Optimize database calls
- Visit our Community Forum
- Check the Troubleshooting Guide
- Contact Support for enterprise assistance