Contributing to Property Management System¶
Thank you for your interest in contributing to the Property Management System! This guide will help you understand our development process, coding standards, and how to effectively contribute to the project.
Table of Contents¶
- Getting Started
- Development Workflow
- Coding Standards
- Testing Guidelines
- Pull Request Process
- Issue Guidelines
- Architecture Guidelines
- Performance Considerations
Getting Started¶
Prerequisites¶
Before you start contributing, ensure you have the following installed:
- .NET 8 SDK (latest version)
- Docker Desktop 4.0+
- Node.js 18+ and npm 9+
- PostgreSQL 14+ (or use Docker)
- Redis 6+ (or use Docker)
- Git with proper configuration
Environment Setup¶
-
Fork and clone the repository
-
Set up your development environment
-
Run the application
-
Verify your setup
Development Workflow¶
Branch Strategy¶
We use GitFlow with the following branch structure:
main: Production-ready codedevelop: Integration branch for featuresfeature/*: Individual features (branch fromdevelop)hotfix/*: Critical production fixes (branch frommain)release/*: Release preparation (branch fromdevelop)
Creating a Feature Branch¶
# Switch to develop and get latest changes
git checkout develop
git pull origin develop
# Create and switch to your feature branch
git checkout -b feature/property-search-enhancement
# Make your changes and commit regularly
git add .
git commit -m "feat: add advanced search filters for properties"
# Push your branch
git push origin feature/property-search-enhancement
Commit Message Format¶
We follow Conventional Commits specification:
Types: - feat: New feature - fix: Bug fix - docs: Documentation changes - style: Code style changes (formatting, etc.) - refactor: Code refactoring - test: Adding or updating tests - chore: Maintenance tasks
Examples:
feat(property): add unit management functionality
fix(auth): resolve JWT token expiration issue
docs(api): update property endpoint documentation
test(property): add integration tests for property creation
Coding Standards¶
C# Backend Standards¶
Code Style¶
- Use C# 12 features including primary constructors and collection expressions
- Enable nullable reference types in all projects
- Use record types for DTOs and value objects
- Follow Clean Architecture principles with clear layer separation
- Use Result
pattern for business logic error handling
Example Patterns¶
// Domain Entity
public class Property : Entity<PropertyId>
{
public string Name { get; private set; } = string.Empty;
public Address Address { get; private set; } = null!;
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
public class PropertyApplicationService
{
public async Task<Result<PropertyResponse>> CreatePropertyAsync(
CreatePropertyCommand command,
CancellationToken cancellationToken = default)
{
var validation = await _validator.ValidateAsync(command, cancellationToken);
if (!validation.IsValid)
return Result.Failure<PropertyResponse>(validation.Errors.First().ErrorMessage);
// Domain logic and persistence
using var transaction = await _unitOfWork.BeginTransactionAsync(cancellationToken);
var propertyResult = Property.Create(command.Name, command.Address, command.Type);
if (propertyResult.IsFailure)
return Result.Failure<PropertyResponse>(propertyResult.Error);
await _propertyRepository.AddAsync(propertyResult.Value, cancellationToken);
await _unitOfWork.SaveChangesAsync(cancellationToken);
await transaction.CommitAsync(cancellationToken);
return Result.Success(propertyResult.Value.ToResponse());
}
}
Documentation Requirements¶
- All public APIs must have XML documentation
- Complex business logic should include explanatory comments
- Domain concepts should be thoroughly documented
/// <summary>
/// Creates a new property with comprehensive validation and business rules.
/// </summary>
/// <param name="command">Property creation command with required details</param>
/// <param name="cancellationToken">Cancellation token for async operation</param>
/// <returns>Result containing the created property or validation errors</returns>
/// <exception cref="ValidationException">Thrown when business rules are violated</exception>
public async Task<Result<PropertyResponse>> CreatePropertyAsync(
CreatePropertyCommand command,
CancellationToken cancellationToken = default)
TypeScript/React Frontend Standards¶
Code Style¶
- Use strict TypeScript configuration with no
anytypes - Prefer Server Components for data fetching in Next.js 15
- Use custom hooks for complex state logic
- Implement proper error boundaries for resilient UX
- Follow functional programming patterns where appropriate
Component Patterns¶
// Server Component
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} />
</div>
);
}
// Client Component with proper typing
interface PropertyFormProps {
initialData?: Partial<Property>;
onSubmit: (data: CreatePropertyRequest) => Promise<void>;
}
export function PropertyForm({ initialData, onSubmit }: PropertyFormProps) {
const { mutate: createProperty, isPending } = useMutation({
mutationFn: onSubmit,
onSuccess: () => toast.success('Property created successfully'),
onError: (error) => toast.error(error.message),
});
return (
<form onSubmit={(e) => { e.preventDefault(); createProperty(formData); }}>
{/* Form implementation */}
</form>
);
}
Styling Guidelines¶
- Use Tailwind CSS for utility-first styling
- Implement responsive design with mobile-first approach
- Follow design system tokens for consistency
- Use semantic HTML for accessibility
Database Standards¶
Entity Framework Configuration¶
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);
});
// Performance indexes
builder.HasIndex(p => p.Name);
builder.HasIndex("Address_City", "Address_State");
}
}
Migration Guidelines¶
- Always create reversible migrations
- Include data migration scripts when needed
- Test migrations against production-like data
- Document breaking changes in migration comments
Testing Guidelines¶
Testing Strategy¶
We follow the Test Pyramid approach: - Many unit tests (70-80% of total tests) - Fewer integration tests (15-25% of total tests) - Minimal end-to-end tests (5-10% of total tests)
Unit Testing Standards¶
C# Unit Tests¶
[TestFixture]
public class PropertyTests
{
private readonly Address _validAddress = new("123 Main St", "Springfield", "IL", "62701");
[Test]
public void AddUnit_WhenPropertyAtCapacity_ShouldReturnFailureWithMessage()
{
// Arrange
var property = PropertyTestBuilder
.Create()
.WithType(PropertyType.SingleFamily)
.WithMaxUnits(1)
.Build();
var existingUnit = Unit.Create("Unit-1").Value;
property.AddUnit(existingUnit);
var newUnit = Unit.Create("Unit-2").Value;
// Act
var result = property.AddUnit(newUnit);
// Assert
result.IsFailure.Should().BeTrue();
result.Error.Should().Contain("Maximum units exceeded");
property.Units.Should().HaveCount(1);
}
}
React Component Tests¶
describe('PropertyForm', () => {
it('should display validation errors when form is submitted with invalid data', async () => {
render(<PropertyForm onSubmit={jest.fn()} />);
fireEvent.click(screen.getByRole('button', { name: /create property/i }));
await waitFor(() => {
expect(screen.getByText(/property name is required/i)).toBeInTheDocument();
expect(screen.getByText(/address is required/i)).toBeInTheDocument();
});
});
it('should call onSubmit with correct data when form is valid', async () => {
const mockOnSubmit = jest.fn();
render(<PropertyForm onSubmit={mockOnSubmit} />);
fireEvent.change(screen.getByLabelText(/property name/i), {
target: { value: 'Test Property' },
});
fireEvent.change(screen.getByLabelText(/address/i), {
target: { value: '123 Test Street' },
});
fireEvent.click(screen.getByRole('button', { name: /create/i }));
await waitFor(() => {
expect(mockOnSubmit).toHaveBeenCalledWith(
expect.objectContaining({
name: 'Test Property',
address: '123 Test Street',
})
);
});
});
});
Integration Testing¶
Use TestContainers for integration tests to ensure consistent test environments:
[TestFixture]
public class PropertyIntegrationTests : IntegrationTestBase
{
[Test]
public async Task CreateProperty_EndToEnd_ShouldPersistCorrectly()
{
var request = new CreatePropertyRequest
{
Name = "Integration Test Property",
Address = "123 Integration St",
Type = PropertyType.Apartment,
Units = 50
};
var response = await TestClient.PostAsJsonAsync("/api/properties", request);
response.StatusCode.Should().Be(HttpStatusCode.Created);
var property = await response.Content.ReadFromJsonAsync<PropertyResponse>();
property.Should().NotBeNull();
// Verify database persistence
var dbProperty = await DbContext.Properties.FindAsync(Guid.Parse(property!.Id));
dbProperty.Should().NotBeNull();
}
}
Test Coverage Requirements¶
- Minimum 80% coverage for new code
- 90%+ coverage for business logic in Domain layer
- Critical paths must have 100% coverage
- All public APIs must have corresponding tests
Pull Request Process¶
Before Creating a PR¶
-
Ensure all tests pass
-
Run code formatting
-
Update documentation if needed
- Add/update tests for new functionality
- Verify performance impact for critical paths
PR Template¶
When creating a pull request, use this template:
## Description
Brief description of the changes made.
## Type of Change
- [ ] Bug fix (non-breaking change that fixes an issue)
- [ ] New feature (non-breaking change that adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
- [ ] Documentation update
## Testing
- [ ] Unit tests added/updated
- [ ] Integration tests added/updated
- [ ] Manual testing completed
- [ ] Performance testing (if applicable)
## Checklist
- [ ] Code follows the style guidelines
- [ ] Self-review of code completed
- [ ] Code is commented, particularly in hard-to-understand areas
- [ ] Corresponding changes to documentation made
- [ ] No new warnings introduced
- [ ] Tests added that prove the fix is effective or feature works
- [ ] New and existing unit tests pass locally
Review Process¶
- Automated checks must pass (CI/CD pipeline)
- At least 2 reviewers must approve
- Code owner approval for architectural changes
- Security review for authentication/authorization changes
- Performance review for database or API changes
Issue Guidelines¶
Reporting Bugs¶
Use the bug report template:
**Bug Description**
Clear and concise description of the bug.
**Steps to Reproduce**
1. Go to '...'
2. Click on '...'
3. Scroll down to '...'
4. See error
**Expected Behavior**
What you expected to happen.
**Actual Behavior**
What actually happened.
**Environment**
- OS: [e.g., Windows 10, macOS 12.0]
- Browser: [e.g., Chrome 96, Safari 15]
- .NET Version: [e.g., 8.0]
- Node.js Version: [e.g., 18.17.0]
**Additional Context**
Any other context about the problem.
Feature Requests¶
Include these details: - Business justification for the feature - User stories describing the need - Acceptance criteria for completion - Technical considerations if known
Architecture Guidelines¶
Clean Architecture Principles¶
- Dependency Rule: Dependencies point inward toward the Domain layer
- Single Responsibility: Each class has one reason to change
- Open/Closed: Open for extension, closed for modification
- Interface Segregation: Clients depend only on methods they use
- Dependency Inversion: Depend on abstractions, not concretions
Service Communication¶
- Synchronous: Use HTTP for immediate consistency requirements
- Asynchronous: Use domain events for eventual consistency
- Error Handling: Implement circuit breakers and retries
- Monitoring: Include comprehensive logging and metrics
Database Design¶
- One database per service in microservices architecture
- Use appropriate indexes for query patterns
- Implement soft deletes for audit requirements
- Version schemas for backward compatibility
Performance Considerations¶
Backend Performance¶
- Use caching strategically with proper invalidation
- Implement pagination for large datasets
- Optimize database queries with appropriate indexes
- Use async/await consistently for I/O operations
- Monitor performance with Application Insights
Frontend Performance¶
- Implement code splitting for large bundles
- Use React.memo for expensive component renders
- Optimize images with Next.js Image component
- Implement proper loading states for better UX
- Measure Core Web Vitals regularly
Database Performance¶
- Monitor query performance with query plans
- Use connection pooling effectively
- Implement read replicas for read-heavy workloads
- Consider partitioning for large tables
- Regular maintenance of indexes and statistics
Getting Help¶
- Documentation: Check the comprehensive docs at
/documentations - Discussions: Use GitHub Discussions for questions
- Issues: Create GitHub Issues for bugs or feature requests
- Code Review: Tag specific team members for specialized review
- Architecture: Consult with the architecture team for design decisions
Thank you for contributing to the Property Management System! Your efforts help make this project better for everyone.