Architecture

Detailed architecture documentation for the Bidder backend application.

Module Structure

The project follows a layered, domain-driven architecture:

bidder-application (entry point)
├── bidder-services (7 domain services, 14 modules)
│   ├── role-model-service-api/impl
│   ├── user-profile-service-api/impl
│   ├── organizations-service-api/impl
│   ├── projects-service-api/impl
│   ├── accounts-service-api/impl
│   ├── campaigns-service-api/impl
│   └── campaign-settings-service-api/impl
├── bidder-libraries (10 shared libraries)
│   ├── context, role-model, errors, util, validation
│   ├── object-mapper, cookies
│   └── state-machine, money, weekly-activity
├── bidder-clients (2 external API clients)
│   ├── keycloak
│   └── wildberries
├── bidder-migrations (Liquibase)
└── bidder-e2e-tests (integration tests)

bidder-application

bidder-services

Each service follows the API/Implementation pattern:

ServicePurpose
role-modelCore authorization (used by all services)
user-profileUser management and profiles
organizationsMulti-tenant organization management
projectsProject management within organizations
accountsWildberries account management
campaignsCampaign lifecycle management
campaign-settingsCampaign configuration

bidder-libraries

LibraryPurpose
contextCaller context DSL for coroutines
role-modelRBAC framework
errorsCentralized error handling
utilCommon utilities
state-machineType-safe state machine framework
moneyType-safe money classes
weekly-activityWeekly schedule bitmap

bidder-clients


Authorization Model

Custom RBAC system separate from Keycloak roles.

Core Concepts:

Flow:

  1. Controller uses withCallerContext({ fromSecurityContext() })
  2. Service calls authorizationManager.authorize(action, resource)
  3. System checks global roles, then resource-specific permissions

Example: See OrganizationsController.kt:51-58 and AuthorizedOrganizationService.kt


Data Access Pattern

Services use DAO pattern with Spring Data R2DBC:

Entity → Repository → DAO Interface → DAO Implementation → Service

Controller Pattern


Domain Model Hierarchy

Organization (top-level tenant)
├── Projects (many-to-many via join table)
├── Accounts (many-to-many via join table)
└── Members (via ORGANIZATION_MEMBERSHIPS)

Project
├── belongs to Organization(s)
├── parent Project (self-referential)
└── Campaigns

Campaign
├── belongs to Project
├── uses Account
└── has Settings

Soft Delete

All entities use soft delete:


Campaign Settings Service

Dedicated service for campaign configuration, separate from lifecycle management.

Facade: CampaignSettingsFacade - unified settings operations

Domain Services:

ServiceSettings
CampaignGeneralSettingsServicePause/managed state
CampaignBudgetSettingsServiceBudget, regions
CampaignScheduleSettingsServiceWeekly schedule
PlacementSettingsServiceZone-specific bids
KeyPhraseServiceTarget/Plus/Minus phrases

Endpoints:


Wildberries Integration

Endpoint Registry Pattern

Single source of truth: WbEndpoints.kt

object WbEndpoints {
    object Marketing {
        val START_CAMPAIGN = Endpoint(HttpMethod.Get, "/adv/v0/start")
        // ...
    }
}

Used by both production clients and WireMock test stubs.

Clients

ClientPurpose
WbGeneralClientSeller info, ping, token validation
WbMarketingClientCampaigns, budgets, bidding

Fail-Fast Pattern

  1. Authorize
  2. Call WB API first
  3. Update DB only after API succeeds

Error Handling

CoreException (base)
├── ValidationException (400) - with List<Violation>
└── Domain exceptions (various codes)

ErrorHandler converts exceptions to REST responses with localized messages.