Property Management System - AI Development Context¶
This file provides comprehensive guidance for AI-assisted development of the Property Management System. Use this context to understand project patterns, coding standards, and common implementation approaches.
Essential Commands¶
.NET Backend Development¶
# Restore dependencies and build
dotnet restore && dotnet build
# Run the complete application with Aspire orchestration
dotnet run --project src/AppHost/PropertyManagement.AppHost
# Expected: Aspire dashboard at https://localhost:15888
# Run specific services for isolated development
dotnet run --project src/Services/Identity/PropertyManagement.Identity.WebApi
dotnet run --project src/Services/Property/PropertyManagement.Property.WebApi
dotnet run --project src/Services/Operations/PropertyManagement.Operations.WebApi
# Database migrations for specific service
dotnet ef migrations add InitialCreate --project src/Services/Property/PropertyManagement.Property.Infrastructure
dotnet ef database update --project src/Services/Property/PropertyManagement.Property.Infrastructure
# Format code and run tests
dotnet format && dotnet test
Frontend Development¶
cd frontend/web
# Install dependencies with exact versions
npm ci
# Development server with hot reload
npm run dev
# Expected: Next.js server at http://localhost:3000
# Type checking and linting
npm run type-check && npm run lint
# Build optimized production bundle
npm run build && npm run start
Testing and Quality¶
# Run all tests with coverage
dotnet test --collect:"XPlat Code Coverage" --logger "console;verbosity=detailed"
# Run specific service tests
dotnet test tests/Unit/Services/Property/PropertyManagement.Property.UnitTests.csproj
dotnet test tests/Integration/Services/Property/PropertyManagement.Property.IntegrationTests.csproj
# End-to-end API testing
dotnet test tests/E2E/PropertyManagement.E2E.Tests.csproj --settings tests.runsettings
Documentation¶
# Serve documentation locally with hot reload
cd documentations && mkdocs serve --dev-addr 127.0.0.1:8000
# Build and deploy documentation
mkdocs build --clean && mkdocs gh-deploy
Code Style Guidelines (for AI context)¶
C# / .NET Patterns¶
- Language Version: Use C# 12 features including primary constructors, collection expressions, and required properties
- Nullable Reference Types: Enabled project-wide, use proper null annotations
- Record Types: Prefer for DTOs, value objects, and immutable data structures
- Async/Await: Use throughout, avoid blocking synchronous calls
- Result Pattern: Use custom Result
type instead of throwing exceptions for business logic failures
// Preferred patterns for AI to follow:
// Primary constructor with validation
public sealed record CreatePropertyRequest(
string Name,
string Address,
PropertyType Type,
int Units)
{
public static implicit operator CreatePropertyCommand(CreatePropertyRequest request) =>
new(request.Name, request.Address, request.Type, request.Units);
}
// Rich domain model with business logic
public class Property : Entity<PropertyId>
{
public string Name { get; private set; } = string.Empty;
public Address Address { get; private set; } = null!;
public PropertyType Type { get; private set; }
// Business logic methods that return Result<T>
public Result<Unit> AddUnit(Unit unit)
{
if (Units.Count >= GetMaxUnitsForType())
return Result.Failure<Unit>("Maximum units exceeded for property type");
Units.Add(unit);
RaiseDomainEvent(new UnitAddedEvent(Id, unit.Id));
return Result.Success(unit);
}
}
// Application service with proper error handling
public class PropertyApplicationService
{
public async Task<Result<PropertyResponse>> CreatePropertyAsync(
CreatePropertyCommand command,
CancellationToken cancellationToken = default)
{
// Input validation
var validation = await _validator.ValidateAsync(command, cancellationToken);
if (!validation.IsValid)
return Result.Failure<PropertyResponse>(validation.Errors.First().ErrorMessage);
// Domain logic
var propertyResult = Property.Create(command.Name, command.Address, command.Type);
if (propertyResult.IsFailure)
return Result.Failure<PropertyResponse>(propertyResult.Error);
// Persistence with transaction
using var transaction = await _unitOfWork.BeginTransactionAsync(cancellationToken);
await _propertyRepository.AddAsync(propertyResult.Value, cancellationToken);
await _unitOfWork.SaveChangesAsync(cancellationToken);
await transaction.CommitAsync(cancellationToken);
return Result.Success(propertyResult.Value.ToResponse());
}
}
TypeScript / React Patterns¶
- Strict TypeScript: Use strict mode with no any types
- Functional Components: Use hooks, avoid class components
- Server Components: Leverage Next.js 15 React Server Components
- Type Safety: Define interfaces for all props and API responses
// Preferred patterns for frontend AI development:
// Strict interface definitions
interface PropertyDetailsProps {
propertyId: string;
isEditable?: boolean;
onUpdate?: (property: Property) => void;
}
// Server component with async data fetching
export default async function PropertyPage({
params
}: {
params: Promise<{ id: string }>
}) {
const { id } = await params;
const property = await getPropertyDetails(id);
if (!property) {
notFound();
}
return (
<div className="space-y-6">
<PropertyHeader property={property} />
<PropertyDetails property={property} />
<PropertyUnits propertyId={property.id} />
</div>
);
}
// Custom hook with proper error handling
export function usePropertyMutation() {
return useMutation({
mutationFn: async (data: CreatePropertyRequest): Promise<Property> => {
const response = await fetch('/api/properties', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data),
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.message || 'Failed to create property');
}
return response.json();
},
onSuccess: (property) => {
toast.success(`Property "${property.name}" created successfully`);
queryClient.invalidateQueries({ queryKey: ['properties'] });
},
onError: (error) => {
toast.error(error.message);
},
});
}
Architecture Patterns¶
Clean Architecture Implementation¶
The system follows Domain-Driven Design with Clean Architecture:
┌─────────────────────────────────────┐
│ Web API Layer │ ← Controllers, Middleware, Configuration
├─────────────────────────────────────┤
│ Application Layer │ ← Use Cases, DTOs, Command/Query Handlers
├─────────────────────────────────────┤
│ Domain Layer │ ← Entities, Value Objects, Domain Services
├─────────────────────────────────────┤
│ Infrastructure Layer │ ← Repositories, External Services, EF Context
└─────────────────────────────────────┘
Service Communication Patterns¶
- Synchronous: Direct HTTP calls for immediate consistency requirements
- Asynchronous: Domain events via message bus for eventual consistency
- API Gateway: Centralized entry point with authentication and routing
- BFF Pattern: Separate backend aggregations for web and mobile clients
Data Access Patterns¶
- Repository Pattern: Abstraction over data access with interfaces in Domain layer
- Unit of Work: Transaction management across multiple repositories
- CQRS: Separate read/write models for complex scenarios
- Dapper for Queries: Raw SQL for complex read operations
- EF Core for Commands: Rich ORM for write operations and simple queries
Testing Patterns¶
Unit Testing Approach¶
// Domain logic testing with rich assertions
[Test]
public void AddUnit_WhenUnitsExceedLimit_ShouldReturnFailure()
{
// Arrange
var property = Property.Create("Test Property", validAddress, PropertyType.SingleFamily).Value;
var existingUnits = Enumerable.Range(1, 10).Select(i => CreateValidUnit($"Unit-{i}"));
foreach (var unit in existingUnits)
property.AddUnit(unit);
var newUnit = CreateValidUnit("Unit-11");
// Act
var result = property.AddUnit(newUnit);
// Assert
result.IsFailure.Should().BeTrue();
result.Error.Should().Contain("Maximum units exceeded");
property.Units.Should().HaveCount(10);
}
// Application service testing with mocks
[Test]
public async Task CreateProperty_WhenValidCommand_ShouldReturnSuccess()
{
// Arrange
var command = new CreatePropertyCommand("Valid Property", validAddress, PropertyType.Apartment, 25);
_validator.Setup(v => v.ValidateAsync(command, It.IsAny<CancellationToken>()))
.ReturnsAsync(new ValidationResult());
// Act
var result = await _service.CreatePropertyAsync(command);
// Assert
result.IsSuccess.Should().BeTrue();
result.Value.Name.Should().Be("Valid Property");
_propertyRepository.Verify(r => r.AddAsync(It.IsAny<Property>(), It.IsAny<CancellationToken>()), Times.Once);
_unitOfWork.Verify(u => u.SaveChangesAsync(It.IsAny<CancellationToken>()), Times.Once);
}
Integration Testing with TestContainers¶
[TestFixture]
public class PropertyIntegrationTests : IntegrationTestBase
{
[Test]
public async Task CreateProperty_EndToEnd_ShouldPersistAndReturnCorrectData()
{
// Arrange - Use test containers for database
var request = new CreatePropertyRequest("Integration Test Property", validAddress, PropertyType.Apartment, 50);
// Act - Call actual API endpoint
var response = await _httpClient.PostAsJsonAsync("/api/properties", request);
// Assert
response.StatusCode.Should().Be(HttpStatusCode.Created);
var property = await response.Content.ReadFromJsonAsync<PropertyResponse>();
property.Should().NotBeNull();
property!.Name.Should().Be("Integration Test Property");
// Verify persistence
var persisted = await _dbContext.Properties.FindAsync(Guid.Parse(property.Id));
persisted.Should().NotBeNull();
}
}
Common Business Patterns¶
Domain Events for Service Communication¶
// Raising domain events in entities
public class Property : Entity<PropertyId>
{
public void CompleteCreation()
{
Status = PropertyStatus.Active;
RaiseDomainEvent(new PropertyCreatedEvent(Id, Name, Address, Type));
}
}
// Handling events in other services
public class PropertyCreatedEventHandler : INotificationHandler<PropertyCreatedEvent>
{
public async Task Handle(PropertyCreatedEvent domainEvent, CancellationToken cancellationToken)
{
// Create default maintenance schedule
await _operationsService.CreateDefaultMaintenanceScheduleAsync(domainEvent.PropertyId);
// Initialize community features
await _communityService.InitializePropertyCommunityAsync(domainEvent.PropertyId);
// Send notifications to stakeholders
await _notificationService.NotifyPropertyCreatedAsync(domainEvent);
}
}
Error Handling and Validation¶
// Custom Result type for operation outcomes
public class Result<T>
{
public bool IsSuccess { get; init; }
public bool IsFailure => !IsSuccess;
public T Value { get; init; } = default!;
public string Error { get; init; } = string.Empty;
public static Result<T> Success(T value) => new() { IsSuccess = true, Value = value };
public static Result<T> Failure(string error) => new() { IsSuccess = false, Error = error };
}
// Global exception handling middleware
public class GlobalExceptionMiddleware
{
public async Task InvokeAsync(HttpContext context, RequestDelegate next)
{
try
{
await next(context);
}
catch (ValidationException ex)
{
await HandleValidationExceptionAsync(context, ex);
}
catch (DomainException ex)
{
await HandleDomainExceptionAsync(context, ex);
}
catch (Exception ex)
{
_logger.LogError(ex, "Unhandled exception occurred");
await HandleGenericExceptionAsync(context, ex);
}
}
}
Database Patterns¶
Entity Configuration¶
// EF Core entity configuration following DDD principles
public class PropertyConfiguration : IEntityTypeConfiguration<Property>
{
public void Configure(EntityTypeBuilder<Property> builder)
{
builder.ToTable("Properties");
// Value object mapping
builder.OwnsOne(p => p.Address, address =>
{
address.Property(a => a.Street).HasColumnName("Address_Street").HasMaxLength(200);
address.Property(a => a.City).HasColumnName("Address_City").HasMaxLength(100);
address.Property(a => a.State).HasColumnName("Address_State").HasMaxLength(50);
address.Property(a => a.ZipCode).HasColumnName("Address_ZipCode").HasMaxLength(10);
});
// Navigation properties with proper cascade behavior
builder.HasMany(p => p.Units)
.WithOne(u => u.Property)
.HasForeignKey("PropertyId")
.OnDelete(DeleteBehavior.Cascade);
// Indexes for performance
builder.HasIndex(p => p.Name);
builder.HasIndex("Address_City", "Address_State");
}
}
Repository Implementation¶
public class PropertyRepository : IPropertyRepository
{
public async Task<Property?> GetByIdAsync(PropertyId id, CancellationToken cancellationToken = default)
{
return await _context.Properties
.Include(p => p.Units)
.FirstOrDefaultAsync(p => p.Id == id, cancellationToken);
}
public async Task<IReadOnlyList<Property>> GetByOwnerIdAsync(UserId ownerId, CancellationToken cancellationToken = default)
{
// Use Dapper for complex queries when needed
const string sql = """
SELECT p.*, u.*
FROM Properties p
LEFT JOIN Units u ON p.Id = u.PropertyId
WHERE p.OwnerId = @OwnerId
ORDER BY p.Name, u.Number
""";
var propertyDict = new Dictionary<PropertyId, Property>();
await _connection.QueryAsync<Property, Unit, Property>(
sql,
(property, unit) =>
{
if (!propertyDict.TryGetValue(property.Id, out var existingProperty))
{
existingProperty = property;
propertyDict.Add(property.Id, existingProperty);
}
if (unit != null)
existingProperty.AddUnit(unit);
return existingProperty;
},
new { OwnerId = ownerId },
splitOn: "Id");
return propertyDict.Values.ToList();
}
}
Performance and Security Considerations¶
Caching Strategies¶
- Use Redis for frequently accessed read-only data (property listings, user preferences)
- Implement cache-aside pattern with proper invalidation on updates
- Cache authentication tokens and user sessions
- Use distributed cache for multi-instance deployments
Security Patterns¶
- JWT tokens with short expiration and refresh token rotation
- Role-based and resource-based authorization
- Input validation at multiple layers (client, API, domain)
- SQL injection prevention through parameterized queries
- Rate limiting and request throttling
Database Optimization¶
- Use appropriate indexes for query patterns
- Implement read replicas for reporting queries
- Use connection pooling and proper timeout configurations
- Monitor query performance and optimize slow queries
- Implement database health checks and circuit breakers
Development Workflow¶
Branch Strategy¶
main: Production-ready code with automated deploymentsdevelop: Integration branch for feature developmentfeature/*: Individual feature branches from develophotfix/*: Critical production fixes from main
Code Quality Gates¶
- All code must pass static analysis and formatting checks
- Unit test coverage must be > 80% for new code
- All public APIs require XML documentation
- Security-sensitive changes require additional review
- Performance-critical paths require benchmarking
CI/CD Pipeline¶
# Pipeline stages executed on every commit
dotnet restore && dotnet build --no-restore
dotnet format --verify-no-changes
dotnet test --collect:"XPlat Code Coverage" --no-build
dotnet pack --no-build --configuration Release
# Additional checks for PR validation
dotnet run --project tests/E2E/PropertyManagement.E2E.Tests
docker build -t property-management:pr-${{ github.event.number }} .
./scripts/security-scan.sh
./scripts/performance-test.sh
This comprehensive context should enable AI tools to understand the project structure, coding patterns, and implementation approaches used throughout the Property Management System.