You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

N层架构中POCOs校验的放置位置探讨

Validation Placement in Your N-Tier Architecture: What Makes Sense?

Great question—validation placement is one of those layered architecture decisions that can trip people up, so let’s unpack this for your specific setup. First, let’s split validation into two distinct categories, since they belong in different layers:

1. Basic Property Validation (POCO-Level Constraints)

This includes rules like "Car name can’t be empty," "ID must be positive," or "Name can’t exceed 50 characters." These are inherent constraints of your domain model (the POCOs), so you have two solid options:

Option A: Keep POCOs Pure with Independent Validators

If you want your Core Layer POCOs to stay as clean data containers (only properties), use a validation library like FluentValidation (or roll your own) to create separate validator classes. You can place these validators in the Core Layer (since they’re tightly coupled to the POCOs) or the Business Layer—either works, but Core Layer keeps all domain-related logic together.

Example validator (FluentValidation):

// Core Layer or Business Layer
public class CarPOCOValidator : AbstractValidator<CarPOCO>
{
    public CarPOCOValidator()
    {
        RuleFor(car => car.Id)
            .GreaterThan(0)
            .WithMessage("Car ID must be a positive integer");
        
        RuleFor(car => car.Name)
            .NotEmpty().WithMessage("Car name cannot be empty")
            .MaximumLength(50).WithMessage("Car name cannot exceed 50 characters");
    }
}

Option B: Enforce Validity in POCO Constructors

If you want to guarantee that no invalid POCO instances ever exist, add validation directly to the POCO’s constructor. This keeps validation close to the data, but adds logic to your otherwise "pure" POCOs—tradeoff depends on your preference for model purity vs. data integrity.

Example constructor validation:

// Core Layer POCO
public class CarPOCO 
{
    public int Id { get; } // Make setters private if you want immutable instances
    public string Name { get; }

    public CarPOCO(int id, string name)
    {
        if (id <= 0)
            throw new ArgumentOutOfRangeException(nameof(id), "Car ID must be positive");
        if (string.IsNullOrWhiteSpace(name))
            throw new ArgumentNullException(nameof(name), "Car name cannot be empty");
        if (name.Length > 50)
            throw new ArgumentException("Car name cannot exceed 50 characters", nameof(name));

        Id = id;
        Name = name;
    }
}

2. Business Rule Validation (Context-Dependent Logic)

This includes rules like "Cannot add a car with a name that already exists" or "Only admins can delete cars." These rules depend on business context, external data (like querying the repository), or cross-cutting business policies—this belongs exclusively in the Business Layer.

Your Business Layer is the right home here because:

  • It has access to the repository interfaces (from Core Layer) needed to check existing data.
  • It’s responsible for orchestrating business logic, not just data movement.

Example implementation in your Business Layer:

// Business Layer service
public class CarService
{
    private readonly ICarRepository _carRepository;
    private readonly IValidator<CarPOCO> _carValidator; // Inject the validator if using Option A

    public CarService(ICarRepository carRepository, IValidator<CarPOCO> carValidator)
    {
        _carRepository = carRepository;
        _carValidator = carValidator;
    }

    public void AddCar(CarPOCO car)
    {
        // Step 1: Run basic property validation
        var validationResult = _carValidator.Validate(car);
        if (!validationResult.IsValid)
        {
            // Throw a custom validation exception or return error details
            throw new ValidationException("Invalid car details", validationResult.Errors);
        }

        // Step 2: Run business rule validation
        var existingCar = _carRepository.GetAllCars().FirstOrDefault(c => c.Name.Equals(car.Name, StringComparison.OrdinalIgnoreCase));
        if (existingCar != null)
        {
            throw new BusinessRuleViolationException("A car with this name already exists");
        }

        // Step 3: Proceed with data access
        _carRepository.AddCar(car); // Assuming you add this method to ICarRepository
    }
}

// Custom exception types (you can define these in Core or Business Layer)
public class ValidationException : Exception { /* ... */ }
public class BusinessRuleViolationException : Exception { /* ... */ }

Final Takeaways

  • Basic POCO validation: Choose between constructor validation (for guaranteed validity) or independent validators (for pure POCOs). Either way, keep it close to the POCOs (Core or Business Layer).
  • Business rule validation: Always put this in the Business Layer—this is where your business logic lives, and it’s the only layer that should orchestrate checks against external data and enforce policies.

内容的提问来源于stack exchange,提问作者X.Otano

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.29 08:12:44