Skip to main content

Best Practices Quick Reference

Recommended patterns for building reliable PearDrop applications. For detailed guidance, see the referenced pages below.

TopicLocationCoverage
TestingTesting PearDrop ApplicationsUnit tests, integration tests, test strategies
RepositoriesRepositories & SpecificationsRepository pattern, specifications, best practices
SecuritySecurity & AuthorizationAuthorization, input validation, data protection
MonitoringOperations & MonitoringLogging, performance monitoring, health checks
CQRS OperationsCQRS OperationsCommand/query execution patterns
Form ValidationForm Validation PatternsBlazor validation integration

Key Patterns Summary

✅ Command Design (Essential)

// Required parameters in constructor
public sealed record CreateProductCommand(
string Name,
decimal Price) : ICommand;

References: CQRS Pattern, Commands with Results

✅ Aggregate Design (Essential)

// Private setters, domain methods encapsulate rules
public sealed class Product : Entity, IAggregateRoot
{
private Product() { } // EF Core
public string Name { get; private set; }

public ResultMonad Activate() { /* business logic */ }
}

References: Aggregates, Bounded Contexts

✅ Query Design (Essential)

// Read-only, use projections, filter at database
var products = await readModels.Products
.Where(p => p.CategoryId == categoryId)
.Select(p => new ProductDto(p.Id, p.Name, p.Price))
.Take(50)
.ToListAsync();

References: Read Models, Search and Filtering

✅ Repository Pattern (Essential)

// Check maybe value before accessing
var productMaybe = await repository.FindOne(
new ByIdSpecification<ProductAggregate>(id),
ct);

if (productMaybe.HasNoValue)
return CommandResult.Failed(ErrorCodes.NotFound);

var product = productMaybe.Value;
product.UpdatePrice(newPrice);

await repository.UnitOfWork.SaveEntitiesAsync(ct);

References: Repositories & Specifications

✅ Validation (Essential)

// Server-side validation with FluentValidation
public sealed class CreateProductCommandValidator : AbstractValidator<CreateProductCommand>
{
public CreateProductCommandValidator()
{
RuleFor(x => x.Name)
.NotEmpty().WithMessage("Name is required")
.MaximumLength(100);

RuleFor(x => x.Price)
.GreaterThan(0).WithMessage("Price must be positive");
}
}

References: Command Validators & Authorizers, Form Validation Patterns

✅ Authorization (Important)

// Check permissions before operating
if (!authResult.Succeeded)
return CommandResult.Failed("Unauthorized");

// Validate in aggregate
if (note.OwnerId != userId)
return ResultMonad.Failure("Cannot modify note you don't own");

References: Security & Authorization, Command Validators & Authorizers

Essential vs. Advanced Patterns

PatternWhen NeededLearn More
Commands & QueriesAlwaysCQRS Pattern
AggregatesAlwaysAggregates
Read ModelsFor query performanceRead Models
RepositoriesAlways (for data access)Repositories & Specifications
ValidationAlwaysValidators
AuthorizationFor securitySecurity & Authorization
Domain EventsFor coordinating aggregatesDomain Events
TestsAlwaysTesting Guide
LoggingFor productionMonitoring Guide

Common Mistakes

❌ Mistake: Public Setters on Aggregates

// BAD - Anyone can change state
public class Order
{
public OrderStatus Status { get; set; } // Public setter!
}

// GOOD - Only methods can change state
public sealed class Order
{
public OrderStatus Status { get; private set; } // Private setter

public ResultMonad Ship()
{
// Validates business rules before changing state
if (this.Status != OrderStatus.Paid)
return ResultMonad.Failure("Cannot ship unpaid order");

this.Status = OrderStatus.Shipped;
return ResultMonad.Success();
}
}

❌ Mistake: Multiple SaveEntitiesAsync Calls

// BAD - Two database transactions
await repository.UnitOfWork.SaveEntitiesAsync(ct);
aggregate.UpdateProperty(newValue);
await repository.UnitOfWork.SaveEntitiesAsync(ct); // Wrong!

// GOOD - Single transaction
aggregate.UpdateProperty(newValue);
await repository.UnitOfWork.SaveEntitiesAsync(ct); // One save

❌ Mistake: Querying Without Pagination

// BAD - Could load millions of rows
var allProducts = await readModels.Products.ToListAsync();

// GOOD - Paginated
var products = await readModels.Products
.Take(50)
.ToListAsync();

❌ Mistake: Trusting Client-Side Validation Only

// BAD - Browser can be bypassed
if (clientSideValidation.IsValid)
await SendToServer(command);

// GOOD - Validate on server
var result = await handler.Handle(command); // Handler has validator
if (!result.IsSuccess)
return BadRequest(result.Error);

Next Steps

Explore detailed guides for specific patterns:

  1. Complete CRUD Feature - See all patterns working together
  2. Testing PearDrop Applications - Comprehensive testing strategies
  3. Security & Authorization - Secure your application
  4. Monitoring & Operations - Production readiness
  5. Repositories & Specifications - Data access patterns

Remember: These are guidelines, not absolute rules. Adapt to your team's needs and project requirements.