|
|
il y a 2 semaines | |
|---|---|---|
| Database | il y a 2 semaines | |
| src | il y a 2 semaines | |
| .gitignore | il y a 2 semaines | |
| ARCHITECTURE_COMPARISON.md | il y a 2 semaines | |
| CODE_REVIEW_SUMMARY.md | il y a 2 semaines | |
| Dockerfile | il y a 2 semaines | |
| ENVIRONMENT_CONFIG.md | il y a 2 semaines | |
| EmbaseConferenceScheduler.slnx | il y a 2 semaines | |
| QUICK_START.md | il y a 2 semaines | |
| README_Architecture.md | il y a 2 semaines |
This project follows Clean Architecture principles with clear separation of concerns across multiple layers, adhering to Microsoft development best practices.
┌──────────────────────────────────────────────────────────────┐
│ Worker Layer (Host) │
│ - Program.cs (Composition Root) │
│ - Quartz Job Definitions │
│ - DI Configuration │
└────────────────────┬─────────────────────────────────────────┘
│
┌───────────┴───────────┐
│ │
┌────────▼──────────┐ ┌────────▼───────────────────┐
│ Application │ │ Infrastructure │
│ - Business Logic │ │ - Database (Dapper) │
│ - Orchestration │ │ - SFTP (SSH.NET) │
│ - Services │ │ - File Operations │
└────────┬──────────┘ └────────┬───────────────────┘
│ │
└───────────┬───────────┘
│
┌────────▼──────────┐
│ Domain │
│ - Entities │
│ - Interfaces │
│ - Configuration │
└───────────────────┘
ConferenceAbstractArticle, DispatchRecord)SftpSettings, PackagingSettings, SchedulerSettings)PackagingService)ConferenceAbstractRepository using Dapper + Npgsql)SftpService, ZipService)Program.cs with DI container setup| Layer | Technologies |
|---|---|
| Framework | .NET 8 |
| Database | PostgreSQL with Dapper ORM |
| Scheduler | Quartz.NET |
| SFTP | SSH.NET |
| Logging | Serilog |
| Containerization | Docker (Linux) |
Embase_Conference_Workflow_Scheduler/
├── EmbaseConferenceScheduler.sln # Solution file
│
├── src/
│ ├── EmbaseConferenceScheduler.Domain/
│ │ ├── Entities/
│ │ │ ├── ConferenceAbstractArticle.cs
│ │ │ └── DispatchRecord.cs
│ │ ├── Interfaces/
│ │ │ ├── IConferenceAbstractRepository.cs
│ │ │ └── IFileServices.cs
│ │ └── Configuration/
│ │ └── Settings.cs
│ │
│ ├── EmbaseConferenceScheduler.Application/
│ │ └── Services/
│ │ └── PackagingService.cs
│ │
│ ├── EmbaseConferenceScheduler.Infrastructure/
│ │ ├── Persistence/
│ │ │ └── ConferenceAbstractRepository.cs
│ │ ├── FileTransfer/
│ │ │ └── SftpService.cs
│ │ └── FileOperations/
│ │ └── ZipService.cs
│ │
│ └── EmbaseConferenceScheduler.Worker/
│ ├── Program.cs
│ ├── Jobs/
│ │ └── ConferenceAbstractPackagingJob.cs
│ ├── Configuration/
│ │ ├── DependencyInjection.cs
│ │ └── QuartzConfiguration.cs
│ ├── appsettings.json # Base/common settings
│ ├── appsettings.Development.json # Dev overrides
│ ├── appsettings.Staging.json # Staging overrides
│ └── appsettings.Production.json # Production overrides
│
├── Database/
│ └── create_tracking_table.sql # Database schema
│
├── Dockerfile # Multi-stage build
├── .gitignore
└── README_Architecture.md # This file
The application uses hierarchical configuration following .NET conventions:
appsettings.json
↓ (overridden by)
appsettings.Development.json / appsettings.Staging.json / appsettings.Production.json
↓ (overridden by)
Environment Variables
↓ (overridden by)
Command-line arguments
| Section | Purpose | Location |
|---|---|---|
ConnectionStrings |
PostgreSQL connection | All appsettings + env vars |
Sftp |
SFTP server configuration | All appsettings + env vars |
Packaging |
File paths and naming | All appsettings |
Scheduler |
Quartz CRON schedule | All appsettings |
Serilog |
Logging configuration | appsettings.json (common) |
All database operations are abstracted through IConferenceAbstractRepository:
public interface IConferenceAbstractRepository
{
Task<IReadOnlyList<ConferenceAbstractArticle>> GetUnprocessedArticlesAsync(CancellationToken ct);
Task<long> GetNextSequenceNumberAsync(CancellationToken ct);
Task SaveDispatchRecordsAsync(IEnumerable<DispatchRecord> records, CancellationToken ct);
}
Implementation uses:
All services are registered in DependencyInjection.cs:
services.Configure<SftpSettings>(config.GetSection(SftpSettings.SectionName));
services.Configure<PackagingSettings>(config.GetSection(PackagingSettings.SectionName));
services.Configure<SchedulerSettings>(config.GetSection(SchedulerSettings.SectionName));
services.AddSingleton<IConferenceAbstractRepository, ConferenceAbstractRepository>();
services.AddSingleton<IZipService, ZipService>();
services.AddSingleton<ISftpService, SftpService>();
services.AddSingleton<IPackagingService, PackagingService>();
Benefits:
# Restore dependencies
dotnet restore
# Build solution
dotnet build
# Run Worker (Development environment)
cd src/EmbaseConferenceScheduler.Worker
dotnet run --environment Development
# Staging
dotnet run --environment Staging
# Production
dotnet run --environment Production
# 1. Create tracking table
psql -d embase -f Database/create_tracking_table.sql
# 2. Configure appsettings files
# Edit src/EmbaseConferenceScheduler.Worker/appsettings.Production.json
# Update database connection, SFTP settings, etc.
# 3. Build Docker image
docker build -t embase-conference-scheduler:latest .
# 4. Run container
docker run -d \
-e DOTNET_ENVIRONMENT=Production \
-v /data/production/articles/pdf:/production/articles/pdf:ro \
-v embase-logs:/logs \
--name embase-conference-scheduler \
embase-conference-scheduler:latest
# 4. View logs
docker logs -f embase-conference-scheduler
Default schedules per environment:
| Environment | CRON | Description |
|---|---|---|
| Development | 0 */5 * * * ? |
Every 5 minutes (testing) |
| Staging | 0 0 3 * * ? |
Daily at 03:00 IST |
| Production | 0 0 2 * * ? |
Daily at 02:00 IST |
docker run -e "Scheduler__CronExpression=0 0 4 * * ?" ...
┌──────────────────────────────────────────────────────┐
│ 1. Scheduler Triggers (Daily CRON) │
└───────────────────┬──────────────────────────────────┘
│
┌───────────────────▼──────────────────────────────────┐
│ 2. Query Unprocessed Articles from PostgreSQL │
│ (tbldiscardeditemreport JOIN tblEmbaseConference │
│ WHERE lotid NOT IN dispatched) │
└───────────────────┬──────────────────────────────────┘
│
┌───────────────────▼──────────────────────────────────┐
│ 3. Get Next Sequence Number (emconflumXXXXXXX) │
└───────────────────┬──────────────────────────────────┘
│
┌───────────────────▼──────────────────────────────────┐
│ 4. Group Articles by SourceId │
│ (One ZIP per source) │
└───────────────────┬──────────────────────────────────┘
│
┌───────────┴───────────┐
│ │
┌───────▼────────┐ ┌────────▼─────────┐
│ 5a. Copy PDFs │ │ 5b. Create ZIP │
│ to temp folder│────▶│ from bundle │
└────────────────┘ └────────┬─────────┘
│
┌────────▼─────────┐
│ 6. Upload SFTP │
└────────┬─────────┘
│
┌────────▼─────────┐
│ 7. Save Dispatch│
│ Records to DB │
└──────────────────┘
- EmbaseConferenceScheduler.Domain.Tests
- EmbaseConferenceScheduler.Application.Tests
- EmbaseConferenceScheduler.Infrastructure.Tests
Mock external dependencies:
IConferenceAbstractRepository → in-memory fakeISftpService → mock SFTPIZipService → mock file system| Pattern | Location | Purpose |
|---|---|---|
| Repository | Infrastructure | Abstract database access |
| Dependency Injection | Worker (Program.cs) | IoC container |
| Options Pattern | All layers | Strongly-typed configuration |
| Factory (Quartz) | Worker | Job instantiation |
| Strategy | Infrastructure | SFTP auth (password vs key) |
appsettings.Development.json - Local development (can commit with dummy values)appsettings.Staging.json - Staging secrets (git-ignored or stored in CI/CD)appsettings.Production.json - Production secrets (git-ignored or stored in CI/CD)Settings are loaded in this priority (last wins):
appsettings.json (base/common settings)appsettings.{Environment}.json (environment-specific)Override via environment variables in Docker:
# In docker-compose.yml or at runtime
environment:
ConnectionStrings__EmbaseDb: "Host=secure-db;Port=5432;Database=embase;Username=user;Password=secret"
Sftp__Password: "sftp-secret-password"
Check:
Check:
Check:
ping, telnet)Proprietary - Elsevier Embase Team
For issues or questions, contact the Embase Data Engineering team.