Documents/planning/Development Guide

Development Guide

Development Guide

Setup, conventions, daily workflow for engineers on the Legacy Modernization project
Tech: .NET 8 | React 18 | Azure Service Bus | Docker | AI-first


1. Getting Started

1.1 Prerequisites

Required:
  □ .NET 8 SDK                   → dotnet --version (8.0+)
  □ Node.js 20 LTS               → node --version (20+)
  □ Docker Desktop                → docker --version
  □ Git                           → git --version
  □ Azure CLI                     → az --version

IDE (choose one):
  □ Cursor Pro (recommended)      → AI-first, agentic mode
  □ VS Code + Extensions          → if Cursor not available
  □ JetBrains Rider               → if preferred

AI Tools:
  □ Cursor Pro license            → request from Tech Lead
  □ Claude Code (CLI)             → npm install -g @anthropic-ai/claude-code
  □ Ollama (local models)         → brew install ollama
  □ CodeRabbit                    → auto-enabled on repo PRs

1.2 Repository Setup

# Clone repo
git clone https://github.com/phoenixdx/product-a-modernization.git
cd product-a-modernization

# Install dependencies
dotnet restore
cd src/frontend && npm ci && cd ../..

# Start local environment
docker-compose up -d

# Verify everything is running
curl http://localhost:5000/health          # API Gateway
curl http://localhost:5001/health          # Travel Service
curl http://localhost:5002/health          # Event Service
curl http://localhost:5003/health          # Comms Service
curl http://localhost:5004/health          # Workforce Service
curl http://localhost:5005/health          # Reporting Service

# Run all tests
dotnet test
cd src/frontend && npm test && cd ../..

1.3 Repository Structure

product-a-modernization/
├── src/
│   ├── gateway/                          ← YARP API Gateway
│   │   └── Gateway.API/
│   │
│   ├── services/                         ← Microservices
│   │   ├── travel-booking/
│   │   │   ├── TravelBooking.API/
│   │   │   ├── TravelBooking.Application/
│   │   │   ├── TravelBooking.Domain/
│   │   │   ├── TravelBooking.Infrastructure/
│   │   │   └── tests/
│   │   │       ├── UnitTests/
│   │   │       ├── IntegrationTests/
│   │   │       └── ContractTests/
│   │   │
│   │   ├── event-management/             ← Same structure
│   │   ├── workforce/
│   │   ├── communications/
│   │   └── reporting/
│   │
│   ├── shared/                           ← Shared NuGet packages
│   │   ├── SharedKernel/                 ← Base entities, events, logging
│   │   └── Contracts/                    ← Shared event contracts
│   │
│   └── frontend/                         ← React 18 SPA
│       ├── src/
│       │   ├── modules/
│       │   │   ├── travel/
│       │   │   ├── events/
│       │   │   ├── reports/
│       │   │   └── shared/               ← Design system components
│       │   ├── App.tsx
│       │   └── main.tsx
│       ├── package.json
│       └── vite.config.ts
│
├── infra/                                ← IaC (Bicep)
│   ├── modules/
│   ├── environments/
│   │   ├── dev.bicepparam
│   │   ├── staging.bicepparam
│   │   └── prod.bicepparam
│   └── main.bicep
│
├── docs/                                 ← Documentation
│   ├── adrs/                             ← Architecture Decision Records
│   ├── runbooks/                         ← Operational runbooks
│   └── api/                              ← OpenAPI specs
│
├── prompts/                              ← AI Prompt Library
│   ├── migration/
│   │   ├── analyze-module.md
│   │   ├── scaffold-service.md
│   │   ├── migrate-business-logic.md
│   │   └── generate-tests.md
│   └── review/
│       ├── security-checklist.md
│       └── business-logic-validation.md
│
├── scripts/                              ← Utility scripts
│   ├── smoke-test.sh
│   ├── seed-data.sh
│   └── monitor-canary.sh
│
├── .github/
│   ├── workflows/                        ← CI/CD pipelines
│   │   ├── service-ci.yml
│   │   ├── frontend-ci.yml
│   │   └── infra-ci.yml
│   └── copilot-instructions.md           ← Copilot project context
│
├── .cursorrules                          ← Cursor AI rules
├── CLAUDE.md                             ← Claude Code project context
├── docker-compose.yml                    ← Local development
└── README.md

2. Coding Conventions

2.1 .NET 8 Backend

Naming:
  Classes:           PascalCase         → BookingService
  Methods:           PascalCase         → CreateBookingAsync()
  Properties:        PascalCase         → BookingId
  Private fields:    _camelCase         → _bookingRepository
  Local variables:   camelCase          → bookingCount
  Constants:         PascalCase         → MaxRetryCount
  Interfaces:        I + PascalCase     → IBookingRepository

Files:
  1 class per file (exceptions: small related DTOs)
  File name = class name
  Folder = namespace

Async:
  All I/O operations MUST be async
  Suffix with Async → GetBookingAsync()
  Use CancellationToken in all async methods

Nullable:
  Nullable reference types ENABLED
  No null without explicit ? annotation
  Use required keyword for non-optional properties

Service Template:

// Controllers — thin, delegate to Application layer
[ApiController]
[Route("api/[controller]")]
public class BookingsController(IMediator mediator) : ControllerBase
{
    [HttpPost]
    public async Task<ActionResult<BookingResponse>> Create(
        CreateBookingRequest request,
        CancellationToken ct)
    {
        var result = await mediator.Send(
            new CreateBookingCommand(request), ct);
        return CreatedAtAction(nameof(GetById),
            new { id = result.Id }, result);
    }
}

// Application — business logic orchestration
public class CreateBookingCommandHandler(
    IBookingRepository repo,
    IEventPublisher events)
    : IRequestHandler<CreateBookingCommand, BookingResponse>
{
    public async Task<BookingResponse> Handle(
        CreateBookingCommand command,
        CancellationToken ct)
    {
        var booking = Booking.Create(command.UserId, command.Destination);
        await repo.AddAsync(booking, ct);
        await events.PublishAsync(new BookingCreated(booking.Id), ct);
        return booking.ToResponse();
    }
}

// Domain — pure business rules, no dependencies
public class Booking
{
    public required Guid Id { get; init; }
    public required string UserId { get; init; }
    public required string Destination { get; init; }
    public BookingStatus Status { get; private set; }

    public static Booking Create(string userId, string destination)
    {
        // Business rules here
        return new Booking
        {
            Id = Guid.NewGuid(),
            UserId = userId,
            Destination = destination,
            Status = BookingStatus.Pending
        };
    }
}

2.2 React 18 Frontend

Naming:
  Components:        PascalCase         → BookingCard.tsx
  Hooks:             useCamelCase       → useBookings.ts
  Utilities:         camelCase          → formatDate.ts
  Types:             PascalCase         → BookingResponse.ts
  Constants:         SCREAMING_SNAKE    → API_BASE_URL

Structure per module:
  modules/travel/
  ├── components/        ← UI components
  ├── hooks/             ← Custom hooks (data fetching, state)
  ├── types/             ← TypeScript types
  ├── services/          ← API calls
  └── index.ts           ← Module exports

Rules:
  • Functional components only (no class components)
  • TypeScript strict mode ON
  • No any — type everything
  • React Query (TanStack Query) for server state
  • Zustand for client state (if needed)
  • Tailwind CSS + shared design system components

2.3 Event Contracts

// src/shared/Contracts/Events/BookingCreated.cs
namespace Contracts.Events;

public record BookingCreated
{
    public required string EventId { get; init; } = Guid.NewGuid().ToString();
    public required string EventType { get; init; } = "travel.booking.created";
    public required string Source { get; init; } = "travel-booking-service";
    public required DateTimeOffset Timestamp { get; init; } = DateTimeOffset.UtcNow;
    public required string CorrelationId { get; init; }
    public required string Version { get; init; } = "1.0";

    // Domain data
    public required Guid BookingId { get; init; }
    public required string UserId { get; init; }
    public required string Destination { get; init; }
    public required decimal TotalAmount { get; init; }
    public required string Currency { get; init; }
}

3. Daily Development Workflow

3.1 Feature Development Flow

1. Pick task from board (Shaped → Building)
   └── Read spec / acceptance criteria

2. Create branch
   └── git checkout -b feature/travel-booking-search

3. Understand context (AI-first)
   └── Cursor: @codebase "how does booking search work in legacy?"
   └── If migrating: read legacy module analysis doc

4. Write tests first (TDD with AI)
   └── Cursor: "Generate contract tests for GET /api/travel/search"
   └── Cursor: "Generate unit tests for BookingSearchService"
   └── Review AI-generated tests — validate business rules

5. Implement (AI-assisted)
   └── Cursor Agent mode: implement across files
   └── Human: review logic, adjust business rules
   └── Run tests: dotnet test

6. Local verification
   └── docker-compose up -d
   └── Manual test in browser / Postman
   └── Check logs: http://localhost:5341 (Seq)

7. Push & PR
   └── git push origin feature/travel-booking-search
   └── Create PR → CodeRabbit auto-reviews
   └── Fix CodeRabbit issues
   └── Request human review

8. After merge
   └── Auto-deploys to Dev
   └── Verify in Dev environment
   └── Move task to Done

3.2 Migration-Specific Flow

1. AI Analyze Legacy Module
   └── Gemini 2.5 Pro (full codebase context) or Cursor @codebase
   └── Use prompt: prompts/migration/analyze-module.md
   └── Output: dependency map, business rules, DB tables

2. AI Scaffold New Service
   └── Cursor Agent: "Scaffold .NET 8 service for [Module]"
   └── Use prompt: prompts/migration/scaffold-service.md
   └── Output: project structure, DI, EF Core, Dockerfile

3. AI Migrate Business Logic
   └── Claude Sonnet 4: translate .NET Framework → .NET 8
   └── Use prompt: prompts/migration/migrate-business-logic.md
   └── ⚠️ MANDATORY: human review every business rule

4. AI Generate Tests
   └── Claude Sonnet 4: generate contract + unit tests
   └── Use prompt: prompts/migration/generate-tests.md
   └── Human: validate test cases cover real scenarios

5. Data Migration Setup
   └── Claude Code: generate CDC configuration
   └── Run CDC in Dev → verify data integrity

6. PR + Review + Deploy
   └── Same as regular flow but deeper review on business logic

4. Testing Guide

4.1 Running Tests

# All tests
dotnet test

# Specific service
dotnet test src/services/travel-booking/tests/UnitTests
dotnet test src/services/travel-booking/tests/ContractTests
dotnet test src/services/travel-booking/tests/IntegrationTests

# With coverage report
dotnet test --collect:"XPlat Code Coverage"
reportgenerator -reports:**/coverage.cobertura.xml \
  -targetdir:coverage-report -reporttypes:Html

# Frontend tests
cd src/frontend
npm test              # unit tests (Vitest)
npm run test:e2e      # E2E tests (Playwright)

# Contract test verification (Pact)
dotnet test --filter "Category=Pact"

4.2 Writing Tests

Unit Test (business logic):

public class BookingTests
{
    [Fact]
    public void Create_WithValidData_ReturnsBooking()
    {
        var booking = Booking.Create("user-1", "Tokyo");

        Assert.NotEqual(Guid.Empty, booking.Id);
        Assert.Equal(BookingStatus.Pending, booking.Status);
    }

    [Fact]
    public void Create_WithEmptyDestination_ThrowsDomainException()
    {
        Assert.Throws<DomainException>(
            () => Booking.Create("user-1", ""));
    }
}

Contract Test (Pact):

public class TravelApiConsumerTests
{
    private readonly IPactBuilderV4 _pact;

    [Fact]
    public async Task GetBooking_WhenExists_ReturnsBooking()
    {
        _pact
            .UponReceiving("a request for a booking")
            .WithRequest(HttpMethod.Get, "/api/travel/bookings/123")
            .WillRespond()
            .WithStatus(200)
            .WithJsonBody(new
            {
                id = "123",
                destination = Match.Type("Tokyo"),
                status = Match.Regex("Pending|Confirmed|Cancelled",
                                      "Pending")
            });

        await _pact.VerifyAsync(async ctx =>
        {
            var client = new TravelApiClient(ctx.MockServerUri);
            var booking = await client.GetBookingAsync("123");
            Assert.Equal("123", booking.Id);
        });
    }
}

5. AI Development Workflow

5.1 Cursor Setup

.cursorrules (in repo root):

You are working on a .NET 8 microservices project for legacy modernization.

Project context:
- Migrating legacy .NET monolith to .NET 8 microservices
- 6 bounded contexts: Travel, Event, Workforce, Comms, Reporting, Payment (legacy)
- Payment stays in legacy monolith Phase 1, accessed via ACL
- Event-driven architecture using Azure Service Bus
- Per-service databases, Clean Architecture per service

Coding rules:
- All async methods use CancellationToken
- All I/O is async/await
- Nullable reference types enabled
- Use records for DTOs and events
- Use MediatR for CQRS command/query handling
- Domain layer has zero external dependencies
- Infrastructure concerns stay in Infrastructure layer
- Use Serilog structured logging with correlation IDs
- Every public API endpoint needs contract test (Pact)

When migrating legacy code:
- Preserve ALL existing behavior (migration, not refactoring)
- Flag ambiguous logic with // TODO: REVIEW
- Update to .NET 8 patterns (async, nullable, records)
- Generate contract tests to verify backward compatibility

5.2 Claude Code Setup

CLAUDE.md (in repo root):

# Project: Product A Modernization

## Build & Run
- Build: `dotnet build`
- Test: `dotnet test`  
- Run locally: `docker-compose up -d`
- Frontend: `cd src/frontend && npm run dev`

## Architecture
- .NET 8 microservices (Clean Architecture)
- React 18 frontend
- Azure Service Bus for events
- YARP API Gateway
- Per-service SQL databases

## Conventions
- Async everywhere with CancellationToken
- MediatR for CQRS
- Pact for contract testing
- Serilog structured logging
- Domain events via Azure Service Bus

## Migration Rules
- Strangler Fig pattern
- Payment frozen Phase 1 (ACL only)
- Every migration has contract tests
- Business logic migration requires human review

5.3 When to Use Which AI Tool

┌──────────────────────────────────────────────────────────┐
│ Task                        │ Tool                       │
│ ─────────────────────────── │ ────────────────────────── │
│ Inline code completion      │ Cursor (Tab)               │
│ Multi-file feature          │ Cursor Agent mode          │
│ Batch migration (10+ files) │ Claude Code (CLI)          │
│ Legacy code analysis        │ Cursor @codebase           │
│ Architecture brainstorm     │ Claude.ai (browser)        │
│ React from mockup           │ GPT-4o (multimodal)        │
│ PR auto-review              │ CodeRabbit (auto)          │
│ Payment code review         │ Ollama + Llama 3.3 (local) │
│ Generate tests              │ Cursor Agent mode          │
│ Debugging                   │ Cursor inline chat         │
└──────────────────────────────────────────────────────────┘

6. Common Tasks (How-To)

6.1 Add a New Service

# 1. Scaffold (AI-assisted)
# In Cursor: "Scaffold new .NET 8 service called WorkforceService
#             following the Travel Booking service pattern"

# 2. Or manually:
mkdir -p src/services/workforce/{Workforce.API,Workforce.Application,\
Workforce.Domain,Workforce.Infrastructure,tests/UnitTests,\
tests/ContractTests,tests/IntegrationTests}

# 3. Add to docker-compose.yml
# 4. Add to API Gateway routes (YARP config)
# 5. Create database (EF Core migration)
# 6. Add CI/CD workflow (.github/workflows/workforce-ci.yml)
# 7. Add to IaC (infra/modules/)

6.2 Add a New API Endpoint

# 1. Define contract (OpenAPI or request/response types)
# 2. Write contract test FIRST (Pact)
# 3. Add controller action
# 4. Add MediatR command/query handler
# 5. Add domain logic (if new business rule)
# 6. Add repository method (if DB access needed)
# 7. Run tests → PR → review

6.3 Publish a Domain Event

// 1. Define event in Contracts
public record BookingCancelled
{
    public required Guid BookingId { get; init; }
    public required string Reason { get; init; }
    // ... standard event fields
}

// 2. Publish from domain handler
await eventPublisher.PublishAsync(
    new BookingCancelled
    {
        BookingId = booking.Id,
        Reason = command.Reason,
        CorrelationId = correlationId
    }, ct);

// 3. Subscribe in another service
public class BookingCancelledHandler
    : IMessageHandler<BookingCancelled>
{
    public async Task HandleAsync(
        BookingCancelled @event, CancellationToken ct)
    {
        // React to event (e.g., send cancellation email)
        await notificationService.SendCancellationNotice(
            @event.BookingId, ct);
    }
}

6.4 Call Legacy Payment (via ACL)

// In Travel Booking Service — call payment through ACL
public class PaymentAclClient(
    HttpClient httpClient,
    ILogger<PaymentAclClient> logger) : IPaymentGateway
{
    public async Task<PaymentResult> ProcessAsync(
        PaymentRequest request, CancellationToken ct)
    {
        // Translate new contract → legacy format
        var legacyRequest = new
        {
            OrderId = request.BookingId.ToString(),
            Amount = request.Amount,
            CurrCode = request.Currency,
            RefNo = request.IdempotencyKey
        };

        var response = await httpClient.PostAsJsonAsync(
            "/api/legacy/payment/process", legacyRequest, ct);

        var legacyResult = await response.Content
            .ReadFromJsonAsync<LegacyPaymentResult>(ct);

        // Translate legacy response → new contract
        return new PaymentResult
        {
            Success = legacyResult?.Status == "OK",
            TransactionId = legacyResult?.TxnId ?? "",
            Status = MapStatus(legacyResult?.Status)
        };
    }
}

7. Troubleshooting

Problem Solution
Docker compose won't start docker-compose down -v && docker-compose up -d (reset volumes)
SQL Server connection refused Wait 30s after docker-compose up (SQL Server startup). Check docker logs sqlserver
Service Bus connection error Check emulator is running: docker ps | grep servicebus
Tests fail locally but pass in CI Check Docker Compose running. Run docker-compose ps
Cursor @codebase not finding code Re-index: Cmd+Shift+P → "Cursor: Reindex"
Claude Code not understanding project Ensure CLAUDE.md exists in repo root with correct context
Port conflict Check lsof -i :5000 — kill conflicting process or change ports
EF Core migration fails dotnet ef database update --project Infrastructure --startup-project API
Contract test fails after API change Update consumer expectations first, then provider. Pact is consumer-driven
CodeRabbit too noisy Adjust .coderabbit.yaml rules in repo root