Files
pos-system/services/ads-analytics-service-net/SERVICE_DOCS.md
Ho Ngoc Hai f3779c4ebe docs: add SERVICE_DOCS.md for all 24 microservices from per-service code audit
Each SERVICE_DOCS.md documents: Overview, API Endpoints, Commands, Queries,
Domain Model, Database Schema, Integration Events, Dependencies, Configuration.
Generated by 23 parallel audit agents reading actual source code.

Key corrections from audit:
- inventory-service: 12 commands/6 queries (was listed as scaffold)
- promotion-service: 12 commands/10 queries (was listed as 0)
- mission-service: 4 commands/7 queries (was listed as 0)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-13 17:54:53 +07:00

17 KiB

AdsAnalyticsService - Service Documentation

1. Overview

Purpose: Ads Analytics microservice for the GoodGo Platform. Provides campaign performance metrics aggregation, custom report generation, breakdown analytics (by age/gender/device/placement), audience insights, performance recommendations, and admin-level platform-wide metrics.

Service Port: 5015 (Development, via launchSettings.json) / 8080 (Docker/Production)

Database: PostgreSQL (ads_analytics_service on Neon cloud)

  • Connection string configured via ConnectionStrings:DefaultConnection or DATABASE_URL env var
  • Current default points to Neon: ep-holy-glitter-a4hongg7-pooler.us-east-1.aws.neon.tech

Framework: .NET 10.0 / C# 14

Architecture: Clean Architecture + CQRS (MediatR)

Solution Structure:

src/
  AdsAnalyticsService.API/          # Web API + Application layer (CQRS handlers)
  AdsAnalyticsService.Domain/       # Domain entities, SeedWork
  AdsAnalyticsService.Infrastructure/ # EF Core DbContext, configs, migrations
tests/
  AdsAnalyticsService.FunctionalTests/  # Integration tests (WebApplicationFactory)
  AdsAnalyticsService.UnitTests/        # Unit tests (empty project, no test files yet)

2. API Endpoints

MetricsController

Base Route: api/v{version}/ads-analytics

Method Route Description Status
GET campaigns/{id}/metrics?startDate&endDate Get aggregated campaign metrics for a date range. Defaults to last 30 days. Returns CampaignMetricsDto with impressions, clicks, conversions, spend, revenue, CTR, CPC, CPA, ROAS. Implemented
GET adsets/{id}/metrics Get ad set metrics. Placeholder (returns 501)
GET ads/{id}/metrics Get ad metrics. Placeholder (returns 501)

ReportsController

Base Route: api/v{version}/ads-analytics/reports

Method Route Description Status
GET / List reports for an advertiser. Params: advertiserId (required), skip, take. Returns List<ReportListDto>. Implemented
POST / Create a new custom report. Body: CreateReportRequest. Query: advertiserId. Returns Guid (report ID). Implemented
GET /{id} Get report by ID. Placeholder (returns 501)
POST /schedule Schedule a recurring report. Body: ScheduleReportRequest. Placeholder (returns 501)
GET /{id}/export?format=csv Export report in given format. Placeholder (returns 501)

BreakdownController

Base Route: api/v{version}/ads-analytics/campaigns

Method Route Description Status
GET /{id}/breakdown?by&startDate&endDate Get campaign breakdown by dimension. Valid by values: age, gender, device, placement. Mock data (returns hardcoded breakdown)

InsightsController

Base Route: api/v{version}/ads-analytics/insights

Method Route Description Status
GET /audience?campaignId&startDate&endDate Get audience insights (age, gender, location, user count, engagement rate). Mock data
GET /performance?advertiserId Get performance insights and recommendations (Low CTR, High CPA, Budget Underspend). Mock data

AdminMetricsController

Base Route: api/v{version}/admin/ads-analytics/metrics

Method Route Description Status
GET /overview?startDate&endDate Platform-wide metrics overview (total impressions, clicks, spend, revenue, active campaigns/advertisers, avg CTR, avg ROAS). Defaults to last 30 days. Implemented (queries real DB; active advertisers count is mocked)
GET /top-campaigns?metric&limit Top performing campaigns. Valid metric values: spend, impressions, clicks, revenue, roas. Default limit: 10. Implemented (queries real DB)
GET /anomalies Anomaly detection. Placeholder (returns 501, planned for ML integration)

AdminReportsController

Base Route: api/v{version}/admin/ads-analytics/reports

Method Route Description Status
GET / List all reports across all advertisers. Params: skip, take (default 50). Implemented
DELETE /{id} Delete a report by ID (admin only). Returns 204 or 404. Implemented

Health Endpoints

Method Route Description
GET /health Full health check (includes PostgreSQL)
GET /health/live Liveness probe (app is running, no dependency checks)
GET /health/ready Readiness probe (includes PostgreSQL check)

3. Commands

CreateReportCommand

  • File: src/AdsAnalyticsService.API/Application/Commands/CreateReportCommand.cs
  • Handler: CreateReportCommandHandler.cs
  • Parameters:
    • AdvertiserId (Guid) - The advertiser who owns the report
    • Name (string) - Report name
    • ReportType (ReportType enum) - Campaign, AdSet, Ad, or Audience
    • StartDate (DateTime) - Report period start
    • EndDate (DateTime) - Report period end
  • Returns: Guid (the newly created report's ID)
  • Behavior: Creates a new Report aggregate, persists it via AdsAnalyticsServiceContext, and logs the creation. The report is created with status Pending.
  • Note: Handler injects AdsAnalyticsServiceContext directly (no repository pattern used for this command).

4. Queries

GetCampaignMetricsQuery

  • File: src/AdsAnalyticsService.API/Application/Queries/GetCampaignMetricsQuery.cs
  • Handler: GetCampaignMetricsQueryHandler.cs
  • Parameters:
    • CampaignId (Guid)
    • StartDate (DateTime)
    • EndDate (DateTime)
  • Returns: CampaignMetricsDto? (null if no metrics found)
    • CampaignId, StartDate, EndDate
    • TotalImpressions (long), TotalClicks (long), TotalConversions (long)
    • TotalSpend (decimal), TotalRevenue (decimal)
    • CTR (decimal, percentage), CPC (decimal), CPA (decimal), ROAS (decimal)
  • Behavior: Queries CampaignMetrics table filtered by campaign ID and date range, aggregates totals and calculates KPIs (CTR, CPC, CPA, ROAS).

GetReportsQuery

  • File: src/AdsAnalyticsService.API/Application/Queries/GetReportsQuery.cs
  • Handler: Inline in same file (GetReportsQueryHandler)
  • Parameters:
    • AdvertiserId (Guid)
    • Skip (int, default 0)
    • Take (int, default 20)
  • Returns: List<ReportListDto> ordered by CreatedAt descending
    • Each item: Id, Name, ReportType, StartDate, EndDate, Status, CreatedAt

5. Domain Model

Aggregates

CampaignMetrics (Aggregate Root)

  • File: src/AdsAnalyticsService.Domain/AggregatesModel/MetricsAggregate/CampaignMetrics.cs
  • Extends: Entity, implements IAggregateRoot
  • Private Fields: _campaignId, _date, _impressions, _clicks, _conversions, _spend, _revenue
  • Public Getters: CampaignId, Date, Impressions, Clicks, Conversions, Spend, Revenue
  • Calculated Properties (not persisted):
    • CTR = clicks / impressions * 100
    • CPC = spend / clicks
    • CPA = spend / conversions
    • ROAS = revenue / spend
  • Behavior Methods:
    • RecordImpression() - Increments impressions counter
    • RecordClick() - Increments clicks counter
    • RecordConversion() - Increments conversions counter
    • AddSpend(decimal amount) - Adds to spend total
    • AddRevenue(decimal amount) - Adds to revenue total
  • Constructor: CampaignMetrics(Guid campaignId, DateTime date) - Creates new metrics record, normalizes date to start of day

Report (Aggregate Root)

  • File: src/AdsAnalyticsService.Domain/AggregatesModel/ReportAggregate/Report.cs
  • Extends: Entity, implements IAggregateRoot
  • Private Fields: _advertiserId, _name, _reportType, _startDate, _endDate, _status, _dataJson
  • Public Getters: AdvertiserId, Name, ReportType, StartDate, EndDate, Status, DataJson, CreatedAt
  • Behavior Methods:
    • MarkAsProcessing() - Sets status to Processing
    • Complete(string dataJson) - Sets data and status to Completed
    • Fail() - Sets status to Failed
  • Constructor: Report(Guid advertiserId, string name, ReportType reportType, DateTime startDate, DateTime endDate) - Creates report with status Pending and CreatedAt = DateTime.UtcNow

Enumerations

ReportType (enum)

  • Campaign = 1
  • AdSet = 2
  • Ad = 3
  • Audience = 4

ReportStatus (enum)

  • Pending = 1
  • Processing = 2
  • Completed = 3
  • Failed = 4

SeedWork (Base Classes)

  • Entity: Base class with Id (Guid), DomainEvents (IReadOnlyCollection), equality by ID
  • IAggregateRoot: Marker interface
  • IRepository: Generic repository interface with UnitOfWork property
  • IUnitOfWork: SaveChangesAsync(), SaveEntitiesAsync() (with domain event dispatch)
  • ValueObject: Immutable value comparison base class
  • Enumeration: Type-safe enum pattern (not used by current aggregates)

Exceptions

  • DomainException: Base domain exception class
  • AdsAnalyticsDomainException: Service-specific domain exception (extends Exception, not DomainException)

6. Database Schema

Database: ads_analytics_service (PostgreSQL) Migration: 20260117181438_InitialCreate

Table: campaign_metrics

Column Type Nullable Default Notes
id uuid NOT NULL - Primary key
campaign_id uuid NOT NULL - Reference to campaign (no FK constraint)
date date NOT NULL - Daily aggregation date
impressions bigint NOT NULL 0 Number of ad impressions
clicks bigint NOT NULL 0 Number of ad clicks
conversions bigint NOT NULL 0 Number of conversions
spend decimal(18,2) NOT NULL 0 Ad spend amount
revenue decimal(18,2) NOT NULL 0 Revenue generated

Indexes:

  • PK_campaign_metrics - Primary key on id
  • idx_campaign_metrics_campaign_id - Index on campaign_id
  • idx_campaign_metrics_campaign_date - Unique composite index on (campaign_id, date) - enforces one metrics record per campaign per day

Table: reports

Column Type Nullable Default Notes
id uuid NOT NULL - Primary key
advertiser_id uuid NOT NULL - Reference to advertiser (no FK constraint)
name varchar(200) NOT NULL - Report name
report_type integer NOT NULL - Enum: 1=Campaign, 2=AdSet, 3=Ad, 4=Audience
start_date date NOT NULL - Report period start
end_date date NOT NULL - Report period end
status integer NOT NULL - Enum: 1=Pending, 2=Processing, 3=Completed, 4=Failed
data_json jsonb NULL - Report data stored as JSON
created_at timestamp with time zone NOT NULL - Creation timestamp

Indexes:

  • PK_reports - Primary key on id
  • idx_reports_advertiser_id - Index on advertiser_id
  • idx_reports_status - Index on status
  • idx_reports_created_at - Index on created_at

Idempotency: ClientRequest (entity exists but no EF configuration/table migration)

  • Id (Guid), Name (string), Time (DateTime)
  • Used via IRequestManager / RequestManager but no corresponding table is created in the migration -- this will fail at runtime if invoked.

7. Integration Events

No integration events are defined. The service does not publish or consume any cross-service events via RabbitMQ or any other message broker.

No domain events are raised by either aggregate. The CampaignMetrics and Report entities have domain event support inherited from the Entity base class, but neither constructor nor behavior method calls AddDomainEvent().


8. Dependencies

NuGet Packages

API Layer (AdsAnalyticsService.API):

Package Version
MediatR 12.4.1
FluentValidation 11.11.0
FluentValidation.DependencyInjectionExtensions 11.11.0
Microsoft.EntityFrameworkCore.Design 10.0.2
Swashbuckle.AspNetCore 7.2.0
Asp.Versioning.Mvc 8.1.0
Asp.Versioning.Mvc.ApiExplorer 8.1.0
AspNetCore.HealthChecks.NpgSql 8.0.2
AspNetCore.HealthChecks.Redis 8.0.1
Hellang.Middleware.ProblemDetails 6.5.1
Serilog.AspNetCore 8.0.3
Serilog.Sinks.Console 6.0.0
Serilog.Sinks.Seq 8.0.0

Domain Layer (AdsAnalyticsService.Domain):

Package Version
MediatR.Contracts 2.0.1

Infrastructure Layer (AdsAnalyticsService.Infrastructure):

Package Version
Microsoft.EntityFrameworkCore 10.0.0
Npgsql.EntityFrameworkCore.PostgreSQL 10.0.0
Microsoft.EntityFrameworkCore.Tools 10.0.0
MediatR 12.4.1
Dapper 2.1.35
Microsoft.Extensions.Http.Polly 9.0.0
Polly 8.5.0
StackExchange.Redis 2.8.16

Test Projects:

Package Version Project
xunit 2.9.2 Both
FluentAssertions 6.12.2 Both
Moq 4.20.72 UnitTests only
Microsoft.AspNetCore.Mvc.Testing 10.0.0 FunctionalTests
Microsoft.EntityFrameworkCore.InMemory 10.0.0 FunctionalTests
Testcontainers.PostgreSql 4.1.0 FunctionalTests
coverlet.collector 6.0.2 Both

Global Build Properties (Directory.Build.props):

  • net10.0, LangVersion 14.0, Nullable enable, TreatWarningsAsErrors true
  • Microsoft.SourceLink.GitHub 8.0.0

External Service Dependencies

  • PostgreSQL (Neon cloud) - Primary data store
  • Redis - Configured in appsettings (localhost:6379) but not used in any code (StackExchange.Redis is a NuGet dependency only)
  • No other service dependencies - This service does not call any other microservices

9. Configuration

appsettings.json

{
  "ConnectionStrings": {
    "DefaultConnection": "postgresql://...@neon.tech/ads_analytics_service?sslmode=require"
  },
  "Redis": {
    "ConnectionString": "localhost:6379"
  },
  "Jwt": {
    "Secret": "your-super-secret-key-min-32-characters",
    "Issuer": "goodgo-platform",
    "Audience": "goodgo-services",
    "AccessTokenExpiryMinutes": 15,
    "RefreshTokenExpiryDays": 7
  }
}

Environment Variables (from .env.example)

Variable Description Default
ASPNETCORE_ENVIRONMENT Runtime environment Development
DATABASE_URL PostgreSQL connection string (fallback) localhost:5432
REDIS_URL Redis connection string localhost:6379
REDIS_PASSWORD Redis password (empty)
JWT_SECRET JWT signing key -
JWT_ISSUER JWT issuer goodgo-platform
JWT_AUDIENCE JWT audience goodgo-services
API_PORT API port 5000
LOG_LEVEL Serilog minimum level Information
SEQ_URL Seq logging endpoint http://localhost:5341

Connection String Resolution Order

  1. ConnectionStrings:DefaultConnection from appsettings
  2. DATABASE_URL environment variable (fallback)

MediatR Pipeline (order)

  1. LoggingBehavior - Logs request name, elapsed time, errors (with Stopwatch)
  2. ValidatorBehavior - Runs FluentValidation validators (throws ValidationException on failure)
  3. TransactionBehavior - Wraps Commands in DB transaction (skips Queries by name suffix check); uses ExecutionStrategy for retry

Docker Configuration

  • Multi-stage build: sdk:10.0 (build) -> aspnet:10.0 (runtime)
  • Non-root user: dotnetuser (UID/GID 1001)
  • Exposed port: 8080
  • Health check: curl -f http://localhost:8080/health/live (30s interval, 3 retries)

Startup Behavior

  • Auto-applies EF Core migrations on startup (catches and logs errors without crashing)
  • Swagger UI available at /swagger in Development environment
  • CORS: Allow any origin/method/header (open policy)

10. Notable Gaps & Observations

  1. No authentication/authorization: No [Authorize] attributes on any controller. JWT config exists in appsettings but is not wired up in Program.cs.
  2. No FluentValidation validators defined: ValidatorBehavior is registered but no AbstractValidator<T> implementations exist for any command.
  3. No repository interfaces/implementations: Both CreateReportCommandHandler and query handlers inject AdsAnalyticsServiceContext directly instead of using the repository pattern defined in SeedWork.
  4. Mock data endpoints: BreakdownController and InsightsController return hardcoded mock data, not from the database.
  5. ClientRequest table missing: IRequestManager/RequestManager are registered in DI but the ClientRequest entity has no EF configuration and no migration creates its table.
  6. Redis not wired: Redis NuGet package is included and connection string is configured, but no Redis usage exists in the codebase.
  7. Dapper not used: Dapper NuGet package is included but never referenced in any query handler.
  8. No unit tests: The AdsAnalyticsService.UnitTests project exists but contains no test files.
  9. Domain events unused: Neither aggregate raises domain events despite inheriting the capability.
  10. No API response wrapper: Controllers return raw DTOs or anonymous objects, not the standard { success: bool, data: T } format documented in CLAUDE.md.