This guide provides detailed instructions for setting up and working with the Milvaion development environment.
- Prerequisites
- Environment Setup
- Project Structure
- Running the Application
- Database Management
- Testing
- Debugging
- UI Development
- Worker Development
- Troubleshooting
| Software | Version | Download |
|---|---|---|
| .NET SDK | 10.0+ | dotnet.microsoft.com |
| Docker Desktop | 20.10+ | docker.com |
| Git | 2.30+ | git-scm.com |
| Node.js | 18+ | nodejs.org |
| Visual Studio | 2022/2026 | visualstudio.com |
- C# Dev Kit
- Docker
- GitLens
- REST Client
- EditorConfig
- C# (ms-dotnettools.csharp)
- C# Dev Kit
- Docker
- GitLens
- REST Client
- EditorConfig
git clone https://github.com/Milvasoft/milvaion.git
cd milvaionCreate and start PostgreSQL, Redis, RabbitMQ, and Seq:
docker compose -f docker-compose.infra.yml up -dVerify services are running:
docker compose -f docker-compose.infra.yml psExpected output:
NAME STATUS PORTS
milvaion-postgres Up (healthy) 5432->5432
milvaion-redis Up (healthy) 6379->6379
milvaion-rabbitmq Up (healthy) 5672->5672, 15672->15672
milvaion-seq Up 5341->5341, 80->80
| Service | URL | Credentials |
|---|---|---|
| RabbitMQ Management | http://localhost:15672 | guest / guest |
| Seq | http://localhost:5341 | (no auth in dev) |
| PostgreSQL | localhost:5432 | postgres / postgres123 |
| Redis | localhost:6379 | (no auth in dev) |
dotnet restorecd src/Milvaion.Api
dotnet ef database updateOr run the API - it auto-migrates on startup in development:
dotnet runAccess the API documentation:
- Scalar UI: http://localhost:5000/api/documentation/index.html
- Dashboard: http://localhost:5000
milvaion/
├── src/
│ ├── Milvaion.Domain/ # Domain entities and enums
│ │ ├── Entities/
│ │ ├── Enums/
│ │ └── JsonModels/
│ │
│ ├── Milvaion.Application/ # Application layer (CQRS)
│ │ ├── Behaviours/ # MediatR pipeline behaviors
│ │ ├── Dtos/ # Data transfer objects
│ │ ├── Features/ # Commands and queries by feature
│ │ │ ├── Jobs/
│ │ │ ├── Occurrences/
│ │ │ ├── Workers/
│ │ │ └── Users/
│ │ ├── Interfaces/ # Repository and service interfaces
│ │ └── Utils/
│ │
│ ├── Milvaion.Infrastructure/ # Infrastructure implementations
│ │ ├── Extensions/ # DI extensions
│ │ ├── Persistence/ # EF Core, repositories
│ │ ├── Services/ # External service implementations
│ │ │ ├── Messaging/ # RabbitMQ
│ │ │ ├── Caching/ # Redis
│ │ │ └── Scheduling/ # Job scheduling
│ │ └── Utils/
│ │
│ ├── Milvaion.Api/ # API layer
│ │ ├── AppStartup/ # Startup configuration
│ │ ├── Controllers/ # API controllers
│ │ ├── Middlewares/ # Custom middlewares
│ │ ├── Migrations/ # EF Core migrations
│ │ ├── BackgroundServices/ # Background services
│ │ ├── Hubs/ # SignalR hubs
│ │ ├── StaticFiles/ # SQL scripts, templates
│ │ └── wwwroot/ # Static files
│ │
│ ├── Sdk/
│ │ ├── Milvasoft.Milvaion.Sdk/ # Client SDK
│ │ └── Milvasoft.Milvaion.Sdk.Worker/ # Worker SDK
│ │
│ ├── Workers/
│ │ ├── HttpWorker/ # HTTP request worker
│ │ ├── SqlWorker/ # SQL execution worker
│ │ ├── EmailWorker/ # Email sending worker
│ │ ├── MilvaionMaintenanceWorker/ # Maintenance worker
│ │ └── SampleWorker/ # Example worker
│ │
│ └── MilvaionUI/ # React dashboard
│ ├── src/
│ ├── public/
│ └── package.json
│
├── tests/
│ ├── Milvaion.UnitTests/ # Unit tests
│ └── Milvaion.IntegrationTests/ # Integration tests
│
├── docs/
│ ├── portaldocs/ # User documentation
│ ├── githubdocs/ # Developer documentation
│ └── src/ # Images and assets
│
├── build/ # Build scripts
│ ├── build-api.ps1
│ ├── build-worker.ps1
│ └── build-all.ps1
│
├── docker-compose.yml # Full stack compose
├── docker-compose.infra.yml # Infrastructure only
└── Milvaion.sln # Solution file
Using CLI:
cd src/Milvaion.Api
dotnet runUsing Visual Studio:
- Open
Milvaion.sln - Set
Milvaion.Apias startup project - Press F5 or click "Start"
Environment Variables:
# Development mode
ASPNETCORE_ENVIRONMENT=Development
MILVA_ENV=dev
# Override connection strings
ConnectionStrings__DefaultConnectionString=Host=localhost;Port=5432;...
MilvaionConfig__Redis__ConnectionString=localhost:6379Sample Worker:
cd src/Workers/SampleWorker
dotnet runHTTP Worker:
cd src/Workers/HttpWorker
dotnet runRun multiple workers:
# Terminal 1
cd src/Workers/SampleWorker && dotnet run
# Terminal 2
cd src/Workers/EmailWorker && dotnet run
# Terminal 3
cd src/Workers/HttpWorker && dotnet runFull stack:
docker compose up -dAPI only (with local workers):
docker compose -f docker-compose.infra.yml up -d
cd src/Milvaion.Api && dotnet runcd src/Milvaion.Api
# Create a new migration
dotnet ef migrations add MigrationName
# Create migration with specific context
dotnet ef migrations add MigrationName --context MilvaionDbContext# Apply all pending migrations
dotnet ef database update
# Apply to specific migration
dotnet ef database update MigrationName
# Rollback to previous migration
dotnet ef database update PreviousMigrationName# Generate SQL script
dotnet ef migrations script -o migration.sql
# Generate idempotent script
dotnet ef migrations script --idempotent -o migration.sql# Drop and recreate database
dotnet ef database drop --force
dotnet ef database update# Using psql
docker exec -it milvaion-postgres psql -U postgres -d MilvaionDb
# Common queries
\dt # List tables
\d "ScheduledJobs" # Describe table
SELECT * FROM "ScheduledJobs" LIMIT 10;# All tests
dotnet test
# Unit tests only
dotnet test tests/Milvaion.UnitTests
# Integration tests (requires infrastructure)
dotnet test tests/Milvaion.IntegrationTests
# With verbose output
dotnet test --verbosity normal
# With coverage
dotnet test --collect:"XPlat Code Coverage"public class CreateJobCommandHandlerTests
{
private readonly Mock<IJobRepository> _repositoryMock;
private readonly CreateJobCommandHandler _handler;
public CreateJobCommandHandlerTests()
{
_repositoryMock = new Mock<IJobRepository>();
_handler = new CreateJobCommandHandler(_repositoryMock.Object);
}
[Fact]
public async Task Handle_ValidCommand_CreatesJob()
{
// Arrange
var command = new CreateJobCommand(new CreateJobDto
{
DisplayName = "Test Job",
WorkerId = "test-worker"
});
// Act
var result = await _handler.Handle(command, CancellationToken.None);
// Assert
result.IsSuccess.Should().BeTrue();
_repositoryMock.Verify(x => x.AddAsync(It.IsAny<ScheduledJob>()), Times.Once);
}
}public class JobsControllerTests : IClassFixture<WebApplicationFactory<Program>>
{
private readonly HttpClient _client;
public JobsControllerTests(WebApplicationFactory<Program> factory)
{
_client = factory.CreateClient();
}
[Fact]
public async Task CreateJob_ValidRequest_ReturnsCreated()
{
// Arrange
var request = new { displayName = "Test Job", workerId = "worker-1" };
// Act
var response = await _client.PostAsJsonAsync("/api/v1/jobs/job", request);
// Assert
response.StatusCode.Should().Be(HttpStatusCode.Created);
}
}Tests use appsettings.Testing.json:
{
"ConnectionStrings": {
"DefaultConnectionString": "Host=localhost;Port=5432;Database=MilvaionTestDb;..."
},
"MilvaionConfig": {
"Redis": {
"ConnectionString": "localhost:6379",
"Database": 1
}
}
}- Set breakpoints in code
- Press F5 to start debugging
- Use Debug windows: Locals, Watch, Call Stack
# In Dockerfile, use Debug configuration
FROM mcr.microsoft.com/dotnet/sdk:10.0 AS debug
WORKDIR /app
COPY . .
RUN dotnet build -c Debug
EXPOSE 5000
EXPOSE 4024 # VSDBG port
ENTRYPOINT ["dotnet", "run", "--no-build"]// Increase log level in appsettings.Development.json
{
"Logging": {
"LogLevel": {
"Default": "Debug",
"Microsoft.EntityFrameworkCore": "Information",
"Milvaion": "Debug"
}
}
}Console:
Logs appear in terminal when running with dotnet run
Seq: Open http://localhost:5341 to view structured logs
Filter by correlation:
CorrelationId = "abc-123"
cd src/MilvaionUI
# Install dependencies
npm install
# Start development server
npm run dev- API runs on http://localhost:5000
- UI dev server runs on http://localhost:5173
- Vite proxies API requests to backend
cd src/MilvaionUI
npm run buildOutput is copied to src/Milvaion.Api/wwwroot/ during build.
MilvaionUI/
├── src/
│ ├── components/ # Reusable components
│ ├── pages/ # Page components
│ ├── hooks/ # Custom React hooks
│ ├── services/ # API service layer
│ ├── stores/ # State management
│ ├── types/ # TypeScript types
│ └── utils/ # Utility functions
├── public/
└── package.json
# Install template
dotnet new install Milvasoft.Templates.Milvaion
# Create worker project
dotnet new milvaion-console-worker -n MyCompany.MyWorkerMyWorker/
├── Program.cs # Entry point
├── appsettings.json # Configuration
├── Jobs/
│ └── MyJob.cs # Job implementations
└── Dockerfile
public class MyJob : IAsyncJob
{
private readonly ILogger<MyJob> _logger;
private readonly IMyService _myService;
public MyJob(ILogger<MyJob> logger, IMyService myService)
{
_logger = logger;
_myService = myService;
}
public async Task ExecuteAsync(IJobContext context)
{
// Log to user-friendly logs (visible in dashboard)
context.LogInformation("Starting job...");
// Parse job data
var data = JsonSerializer.Deserialize<MyJobData>(context.Job.JobData);
// Check cancellation
context.CancellationToken.ThrowIfCancellationRequested();
// Execute business logic
await _myService.ProcessAsync(data, context.CancellationToken);
context.LogInformation("Job completed!");
}
}// Program.cs
var builder = Host.CreateApplicationBuilder(args);
// Register your services
builder.Services.AddScoped<IMyService, MyService>();
builder.Services.AddHttpClient<IExternalApi, ExternalApiClient>();
// Register Milvaion Worker SDK
builder.Services.AddMilvaionWorkerWithJobs(builder.Configuration);
var host = builder.Build();
await host.RunAsync();{
"JobConsumers": {
"MyJob": {
"ConsumerId": "my-job-consumer",
"MaxParallelJobs": 10,
"ExecutionTimeoutSeconds": 300,
"MaxRetries": 3,
"BaseRetryDelaySeconds": 10
}
}
}# Check if container is running
docker ps | grep postgres
# Check logs
docker logs milvaion-postgres
# Restart container
docker compose -f docker-compose.infra.yml restart postgres# Check container status
docker logs milvaion-rabbitmq
# Verify management UI works
curl http://localhost:15672# Reset migrations
rm -rf src/Milvaion.Api/Migrations/*
dotnet ef migrations add Initial
dotnet ef database update# Find process using port
netstat -ano | findstr :5000
# Kill process (Windows)
taskkill /PID <PID> /F
# Or change port in launchSettings.json- Check worker ID matches job's
workerId - Verify RabbitMQ connection
- Check queue bindings in RabbitMQ Management UI
- Review worker logs for errors
- Check existing GitHub Issues
- Ask in Discussions
- Review Documentation
- Architecture Guide - Understand the system design
- Contributing Guide - Contribute to the project
- API Reference - API documentation
- Worker SDK Guide - Build custom workers