Skip to content

Conversation

@pwdel
Copy link
Member

@pwdel pwdel commented Oct 20, 2025

BACKGROUND

  • Refactoring handlers → domain service → repository is will allow us to lift markets/ into its own microservice with minimal churn.
  • To split it out, after we build the domain+repo for all needed parts, we just replace one adapter in the monolith with a thin HTTP client and move the current domain+repo into a new service process.

HIGH LEVEL OVERVIEW

Markets Flow (new layered architecture)

flowchart TB
    client["Client"]
    handlers["handlers/markets/*.go<br/>• JSON ⇄ DTO mapping<br/>• call service interface"]
    dto["handlers/markets/dto<br/>• DTO definitions"]
    container["internal/app/container<br/>• wires handler → service → repository"]
    domain["internal/domain/markets<br/>• business logic<br/>• validation & orchestration"]
    repo["internal/repository/markets<br/>• persistence (GORM)"]
    db["Database"]

    client --> handlers
    handlers --> dto
    dto --> container
    container --> domain
    domain --> repo
    repo --> db
Loading

Markets Flow (Legacy Architecture)

Handlers: HTTP-only glue (JSON in/out).
Domain internal/domain/markets: business API ⇒ the “service contract”.
Repository internal/repository/markets: persistence adapter (GORM).
Container: composes repo → service → handler.
flowchart TB
    client["Client"]
    handlers["handlers/bets/*.go<br/>• request parsing<br/>• business logic<br/>• direct util.GetDB access"]
    db["Database"]

    client --> handlers --> db
Loading

Execution Plan

  • Where does this get us in terms of migrating to Microservices?

Execution Plan for Migration to Microservices

💡 ARCHITECTURAL STRUCTURE - PER DIRECTORY, PART OF THE CODE

OVERVIEW:

  • Complete Domain Layer - All business logic patterns established
  • Repository Pattern - Data access cleanly separated
  • DTO Pattern - HTTP contracts separate from domain models
  • Service Interfaces - Clean dependency injection ready
  • Error Mapping - Domain errors → HTTP status codes
  • Build Integrity - System compiles and functions

Microservices Readiness:

The architecture now supports easy service extraction:

// Current: Direct repository
svc := domain.NewService(gormRepo, userSvc, clock, config)

// Future: Remote service client  
svc := domain.NewService(httpClientRepo, userSvc, clock, config)

📋 COMPLETION GUIDE

For each remaining handler file, follow this proven pattern:

  1. Import Changes:

    // Remove:
    import "socialpredict/models"
    import "gorm.io/gorm"
    
    // Add:
    import "socialpredict/handlers/markets/dto"
    import dmarkets "socialpredict/internal/domain/markets"
  2. Handler Signature:

    // Old: Direct dependencies
    func Handler(w http.ResponseWriter, r *http.Request)
    
    // New: Service injection  
    func NewHandler(svc dmarkets.Service) *Handler
  3. Logic Movement:

    // Move validation → domain.Service methods
    // Move DB calls → repository.Repository methods  
    // Keep HTTP parsing/response → handler

EXAMPLE createmarket.go PATTERN

✅ FULLY COMPLETED HANDLER

createmarket.go - COMPLETE REFACTOR ✅

  • Before: Mixed HTTP + business logic + GORM + models
  • After: HTTP-only handler using domain service + DTOs
  • GORM import: Removed completely
  • Models import: Removed completely
  • DTOs: Uses dto.CreateMarketRequestdto.CreateMarketResponse
  • Domain service: Calls dmarkets.Service.CreateMarket()
  • Error mapping: Domain errors → HTTP status codes

GUARD CHECK PROGRESS

# Before refactoring:
GORM imports remaining: 3
Models imports remaining: 7

# After refactoring:
GORM imports remaining: 3 (unchanged - focused on one file)
Models imports remaining: 6 (down by 1 - createmarket.go ✅)

🏗️ ARCHITECTURAL PATTERN PROVEN

Complete Clean Handler Pattern (createmarket.go):

1. HTTP-Only Handler:

func (h *CreateMarketHandler) Handle(w http.ResponseWriter, r *http.Request) {
    // 1. Parse request DTO
    var req dto.CreateMarketRequest
    json.NewDecoder(r.Body).Decode(&req)
    
    // 2. Convert to domain request
    domainReq := dmarkets.MarketCreateRequest{...}
    
    // 3. Call domain service
    market, err := h.svc.CreateMarket(ctx, domainReq, username)
    
    // 4. Map errors to HTTP status
    switch err { case dmarkets.ErrUserNotFound: ... }
    
    // 5. Convert to response DTO
    response := dto.CreateMarketResponse{...}
    json.NewEncoder(w).Encode(response)
}

2. Domain Service (business logic):

func (s *Service) CreateMarket(ctx, req, username) (*Market, error) {
    // All validation, orchestration, business rules
    // No HTTP knowledge, no GORM imports
}

3. Repository (data access):

func (r *Repository) Create(ctx context.Context, m *Market) error {
    // GORM operations, database access
    // No business logic, no HTTP
}

FILES STATUS

MARKETS

File / Area Current State Status Notes & Next Action
handlers/markets/handler.go Uses injected domain + auth services; no direct DB access ✅ Done Legacy struct is clean; can eventually retire once standalone handlers cover every route.
handlers/markets/createmarket.go Service-backed create endpoint with AuthService authorization ✅ Done Domain owns validation + persistence; nothing left to decommission.
handlers/markets/listmarkets.go Factory delegating to svc.List / ListByStatus with DTO mapping ✅ Done Continue routing through this implementation; legacy wrapper can be removed when convenient.
handlers/markets/getmarkets.go Thin alias around svc.ListMarkets for backward compat ✅ Done Safe to keep until routes converge.
handlers/markets/marketdetailshandler.go Service-injected details handler returning DTO ✅ Done Expand tests only when new response fields surface.
handlers/markets/marketprojectedprobability.go Fully wired HTTP endpoint; domain ProjectProbability computes real projections via WPAM math ✅ Done Projection math + tests live in the domain; no further handler work required.
handlers/markets/resolvemarket.go Service-injected resolve endpoint using auth façade ✅ Done Optional future cleanup: share auth helpers if we add more admin-only actions.
handlers/markets/searchmarkets.go Service-backed search with sanitization and DTO response ✅ Done Legacy search code removed; nothing pending.

USERS

File / Area Current State Status Notes & Next Action
handlers/users/apply_transaction.go Legacy file removed; all balance adjustments route through internal/domain/users.Service.ApplyTransaction via handlers/services ✅ Done Keep using the domain service so handlers never call util.GetDB directly.
handlers/users/changedescription.go Service-backed handler calling svc.UpdateDescription; auth via auth façade ✅ Done
handlers/users/changedisplayname.go Service-backed handler calling svc.UpdateDisplayName; auth via auth façade ✅ Done
handlers/users/changeemoji.go Service-backed handler calling svc.UpdateEmoji; auth via auth façade ✅ Done
handlers/users/changepassword.go Service-backed handler calling svc.ChangePassword; hashing handled in domain ✅ Done Domain tests cover success/error paths.
handlers/users/changepersonallinks.go Service-backed handler calling svc.UpdatePersonalLinks; auth via auth façade ✅ Done
handlers/users/financial.go Service-backed handler returning svc.GetUserFinancials ✅ Done Covered by domain + handler tests.
handlers/users/listusers.go Thin HTTP façade delegating to svc.ListUserMarkets ✅ Done
handlers/users/privateuser/privateuser.go Service-backed private profile handler using auth façade ✅ Done Future consolidation optional if DTOs expand.
handlers/users/publicuser.go Service-backed handler returning svc.GetPublicUser ✅ Done
handlers/users/publicuser/portfolio.go Service-backed handler returning svc.GetUserPortfolio ✅ Done
handlers/users/publicuser/publicuser.go Duplicate implementation removed; routes share the service-backed handler ✅ Done
handlers/users/userpositiononmarkethandler.go Handler composes markets + users services (auth façade enforced); position math lives in domain ✅ Done
handlers/users/credit/usercredit.go Service-backed handler returning svc.GetUserCredit with missing-user fallback ✅ Done

FINANCIALS and MATH

File / Area Current State Status Notes & Next Action
financials/financialsnapshot.go Implemented inside internal/domain/analytics.Service.ComputeUserFinancials; metrics + user endpoints consume it ✅ Done Ready for general use; expand load-testing only if we onboard heavier portfolios.
financials/systemmetrics.go Served via analytics.Service.ComputeSystemMetrics; metrics handler injects analytics service ✅ Done All callers switched to the service; no further action required.
financials/workprofits.go Rebuilt behind analytics domain/service with repository + tests ✅ Done Keep monitoring accuracy as new profit types are added.
market/dust.go Pure math helper under internal/domain/math/market; buy/sell flows reference domain service wrappers ✅ Done No action needed; façade already shields handlers.
market/marketvolume.go Volume calculations now invoked through the markets domain service (bets fetched via repository layer) ✅ Done Watch for performance regressions on very large bet sets.
outcomes/dbpm/marketshares.go Pure math under internal/domain/math/outcomes/dbpm; consumed via positions math service ✅ Done Nothing pending.
payout/resolvemarketcore.go Logic absorbed into internal/domain/markets.Service.ResolveMarket ✅ Done Legacy helpers removed; docs updated to match new flow.
positions/positionsmath.go & related modules Wrapped by positions domain service; no direct GORM usage from handlers ✅ Done Further tweaks only if we add new position types.
positions/adjust_valuation.go Runs through positions service with repository dependencies injected ✅ Done Covered by resolve/settlement tests.
positions/earliest_users.go Moved behind repository method with deterministic tests; exposed via positions service ✅ Done No outstanding work.
probabilities/wpam/*.go Pure math utilities packaged under domain math probabilities and accessed via service abstractions ✅ Done Optional future enhancement: dedicated probabilities microservice.
README_MATH.md Updated to describe the new analytics + market service façades and math ownership ✅ Done Keep in sync as additional math modules migrate.

BETS

File Current State Still Tied to DB/Models? Status Notes / Next Action
handlers/bets/betshandler.go Service-backed GET /markets/{id}/bets via markets svc No ✅ Done Uses domain service output; nothing pending.
handlers/bets/listbetshandler.go Removed; behaviour now lives in markets service ✅ Done Replace references with betshandler.go wiring if any linger.
handlers/bets/buying/buypositionhandler.go Thin HTTP layer calling bets service No ✅ Done Domain/service tests cover validation and balance checks.
handlers/bets/selling/sellpositionhandler.go Thin HTTP layer calling bets service No ✅ Done Service exposes dust-cap errors; handler only maps responses.
handlers/bets/selling/sellpositioncore.go Deleted during sell-flow migration ✅ Done Dust/share math now in bets.Service.calculateSale.
handlers/bets/betutils/*.go Deleted legacy DB helpers ✅ Done Validation handled by bets domain service/repo.
handlers/bets/selling/sellingdust.go Deleted stub ✅ Done Dust reporting handled by bets service & DTOs.
handlers/bets/errors.go Deleted; ErrDustCapExceeded lives in bets domain ✅ Done Handler asserts against domain error type.
handlers/bets/market_status_validation_test.go Deleted with legacy helpers ✅ Done Covered by bets service tests (e.g., market closed).
handlers/bets/listbetshandler.go (tests) Deleted with handler removal ✅ Done Market bet history now tested in service_marketbets_test.go.

MIDDLEWARE

File / Area Current State Status Notes & Next Action
middleware/auth_legacy.go File removed; DB-backed helper eliminated ✅ Done Nothing left to migrate
internal/service/auth (formerly middleware) Auth façade with Authenticator interface and AuthService in place ✅ Done Continue using as canonical API for authentication/authorization
handlers/markets/handler.go Uses injected AuthService facade ✅ Done No further work
handlers/markets/createmarket.go Auth now routed through AuthService ✅ Done
Other handlers (admin, etc.) All migrated to service-based auth helpers ✅ Done Continue to inject AuthService for any new handlers
Container wiring (internal/app) Builds and exposes AuthService; handlers receive it via DI ✅ Done
Middleware packaging (legacy) Migrated under internal/service/auth; mixed responsibilities resolved ✅ Done Document new location/usage if needed

OpenAPI Update

  • What was previously merely a server based system is now a contract-based system using OpenAPI.
  • Documentation can be found in openapi.yaml in backend/
  • Below is an accounting of all routes in the server and openapi.yaml as well as anything deprecated.
Route In server.go In docs/openapi.yaml Notes
GET /v0/home
POST /v0/login
GET /v0/setup
GET /v0/stats
GET /v0/system/metrics
GET /v0/global/leaderboard
GET /v0/markets
GET /v0/markets/search
GET /v0/markets/active
GET /v0/markets/closed
GET /v0/markets/resolved
GET /v0/markets/{id}
GET /v0/marketprojection/{marketId}/{amount}/{outcome}
GET /v0/markets/bets/{marketId}
GET /v0/markets/positions/{marketId}
GET /v0/markets/positions/{marketId}/{username}
GET /v0/markets/{id}/leaderboard
GET /v0/userinfo/{username}
GET /v0/usercredit/{username}
GET /v0/portfolio/{username}
GET /v0/users/{username}/financial
GET /v0/privateprofile
POST /v0/changepassword
POST /v0/profilechange/displayname
POST /v0/profilechange/emoji
POST /v0/profilechange/description
POST /v0/profilechange/links
POST /v0/markets/{id}/resolve Canonical route; frontend now uses this path.
POST /v0/bet
GET /v0/userposition/{marketId}
POST /v0/sell
POST /v0/create Legacy alias to POST /v0/markets.
POST /v0/admin/createuser
GET /v0/content/home
PUT /v0/admin/content/home

@pwdel pwdel self-assigned this Oct 20, 2025
@pwdel pwdel changed the title Updating refactor of markets handler. Pulling Markets Out of Handlers, Separating Business Logic Oct 21, 2025
@pwdel pwdel marked this pull request as draft October 21, 2025 18:30
@pwdel
Copy link
Member Author

pwdel commented Oct 21, 2025

Migration Plan for Migrating to Microservices:

6-step extraction plan (no big-bang)

1) Freeze the interface

Make sure your domain service interface is what other code depends on (not concrete types):

// internal/domain/markets/service.go
type Service interface {
    Create(ctx context.Context, a CreateArgs) (*Market, error)
    List(ctx context.Context, f ListFilter) ([]Market, error)
    GetDetails(ctx context.Context, id int64) (*MarketDetails, error)
    Resolve(ctx context.Context, id int64, result string) error
    Search(ctx context.Context, q string, p Page) ([]Market, error)
    // ...anything others need
}

Keep callers elsewhere (bets, positions, stats) talking to this interface.

2) Publish a contract (OpenAPI)

Document the HTTP endpoints the markets-api will expose. You can mirror your existing routes or evolve them slightly. Place it at README/BACKEND/API/markets/openapi.yaml. This spec lets you:

Validate server and client.

Generate a client adapter for the monolith (go/types).

Minimal example (sketch):

openapi: 3.0.3
info: { title: markets-api, version: 1.0.0 }
paths:
  /v0/markets:
    get: { ... }       # list/search
    post: { ... }      # create
  /v0/markets/{id}:
    get: { ... }       # details
  /v0/markets/{id}/resolve:
    post: { ... }      # resolve
components: { schemas: ... }

3) Create the new service process

New directories in a separate repo or under services/markets:

services/markets/
  cmd/markets-api/
    main.go            # start HTTP server, routes, health, read config
    Dockerfile
  internal/
    domain/markets/    # move your current domain
    repository/markets # move your GORM repo
  migration/           # if markets owns its own DB
  README.md

Server skeleton (reuses your handler package or a slimmed copy):

func main() {
  db := connectDB()
  econ := setup.EconomicsConfig()
  repo := rmarkets.NewGormRepository(db)
  svc  := dmarkets.NewService(repo, sysClock{}, dmarkets.Config{Econ: econ})
  h    := hmarkets.NewHandler(svc) // or factory functions returning http.HandlerFunc

  r := chi.NewRouter()
  r.Get("/health", func(w http.ResponseWriter, r *http.Request){ w.Write([]byte("ok")) })
  r.Get("/v0/markets", h.List)
  r.Get("/v0/markets/{id}", h.GetDetails)
  r.Post("/v0/markets", h.Create)
  r.Post("/v0/markets/{id}/resolve", h.Resolve)
  r.Get("/v0/markets/search", h.Search)

  http.ListenAndServe(":8081", r)
}

Dockerfile (simple):

FROM golang:1.22 as build
WORKDIR /src
COPY . .
RUN CGO_ENABLED=0 go build -o /out/markets-api ./cmd/markets-api

FROM gcr.io/distroless/static:nonroot
WORKDIR /
COPY --from=build /out/markets-api /markets-api
USER nonroot:nonroot
EXPOSE 8081
ENTRYPOINT ["/markets-api"]

4) Add an HTTP client adapter to the monolith

Implement the same Service interface but backed by HTTP calls to markets-api. Put it here:

backend/internal/repository/marketsclient/
  client.go

Example:

package marketsclient

type HTTPService struct {
  baseURL string
  http    *http.Client
}

func NewHTTPService(base string, c *http.Client) *HTTPService { ... }

// Ensure it implements dmarkets.Service
var _ dmarkets.Service = (*HTTPService)(nil)

func (s *HTTPService) List(ctx context.Context, f dmarkets.ListFilter) ([]dmarkets.Market, error) {
  // Build URL: base/v0/markets?status=&limit=&offset=
  // Do request with context, parse JSON into domain models (map DTO→domain)
  // Map HTTP -> domain errors
}

You can generate this client from your OpenAPI spec and wrap it to return your domain models—very little code.

5) Toggle via config

In your internal/app/container.go (monolith), add a flag to pick local vs. remote:

type Mode string
const (
  LocalRepo Mode = "local"
  RemoteSvc Mode = "remote"
)

func BuildMarkets(db *gorm.DB, econ *setup.EconomicConfig, mode Mode, marketsBaseURL string) *hmarkets.Handler {
  var svc dmarkets.Service
  switch mode {
  case RemoteSvc:
    svc = marketsclient.NewHTTPService(marketsBaseURL, &http.Client{Timeout: 3 * time.Second})
  default:
    repo := rmarkets.NewGormRepository(db)
    svc  = dmarkets.NewService(repo, sysClock{}, dmarkets.Config{Econ: econ})
  }
  return hmarkets.NewHandler(svc)
}

Local (default): monolith behaves as now.

Remote: monolith calls the external markets-api. No calling code changes—still uses the service interface.

6) Deploy side-by-side (strangler pattern)

Run the new markets-api (port 8081).

Flip monolith’s env: MARKETS_MODE=remote and MARKETS_BASE_URL=http://markets-api:8081.

Verify endpoints through the monolith still work.

When stable, stop including the local repo path in the monolith build, or remove it later.

Cross-service concerns (add once, reap forever)

DB per service: markets-api owns the markets DB tables. No other service writes them.

Auth: monolith forwards JWT to markets-api (shared public key). Verify in both.

Idempotency: write endpoints (POST /markets, POST /resolve) accept an Idempotency-Key; server dedups.

Timeouts & retries: client uses timeouts + limited retries with jitter.

Observability: propagate Traceparent/X-Request-ID; log JSON; emit RED metrics; add /health and /ready.

Backpressure: paginate list/search; document limits in OpenAPI.

Contracts in CI: validate openapi.yaml; run client/server contract tests; block breaking changes.

Events (optional): if others react to market changes, publish domain events (outbox) to a bus (Kafka/NATS) when markets change.

What changes for other modules (bets, positions, users, stats)

Repeat the exact pattern:

Make each a clean slice (handler → domain → repository).

Publish an OpenAPI per service.

Extract to services/ with its own Dockerfile and DB.

In the monolith, replace local repo/service with HTTP client that implements the same interface you established during the refactor.

Toggle with config; migrate one domain at a time.

TL;DR (why this works with minimal churn)

All callers depend on the Service interface you already defined.

Extraction = swap the implementation, not rewrite the callers.

OpenAPI + generated client keeps the HTTP plumbing small and safe.

Config flag lets you migrate & rollback safely.

@pwdel
Copy link
Member Author

pwdel commented Oct 24, 2025

Ok so, part of what I'm learning here is ... for anything we migrate over to a service model, the remainder of any endpoints that are entangled with that monolithic service also have to migrated, or if not, at least there has to be some kind of adapter in order for everything to work properly as the app had previously.

@pwdel pwdel changed the title Pulling Markets Out of Handlers, Separating Business Logic Phase 1 of Phased Refactor of Handlers, Separating Business Logic Oct 24, 2025
@pwdel pwdel added the enhancement New feature or request label Oct 28, 2025
@pwdel
Copy link
Member Author

pwdel commented Dec 15, 2025

Reduced cyclomatic complexity on all non-test functions to less than 9.

pwdel and others added 3 commits December 15, 2025 05:59
* Add checkpoint for git commit timing experiment

* Adding SRP explainer checkpoint.

This brief description of the Single Responsibility Principle (SRP) is intended for "vibe coding" tools like ChatGPT Codex to read through and use as a basis for evaluating and generating code.

* Codex evaluation checkpoint

Codex evaluation of SRP adherence in CMS service.go

* Evaluate SRP adherence

Evaluating SRP adherence of `/internal/domain/users/service.go` using Codex

* initial redesign for /users/service.go?
@pwdel
Copy link
Member Author

pwdel commented Jan 4, 2026

OK, starting project management for remaining tasks:

SRP

  • Relevant PR's here
Area File Assignee Status
analytics backend/internal/domain/analytics/financialsnapshot_test.go Patrick
analytics backend/internal/domain/analytics/leaderboard_test.go Patrick
analytics backend/internal/domain/analytics/models.go Patrick [ ]
analytics backend/internal/domain/analytics/repository.go Patrick [ ]
analytics backend/internal/domain/analytics/service.go Patrick
analytics backend/internal/domain/analytics/systemmetrics_integration_test.go Patrick [ ]
analytics backend/internal/domain/analytics/systemmetrics_test.go Patrick [ ]
bets backend/internal/domain/bets/errors.go Patrick [ ]
bets backend/internal/domain/bets/models.go Patrick [ ]
bets backend/internal/domain/bets/service_helpers_test.go Patrick [ ]
bets backend/internal/domain/bets/service_test.go Patrick [ ]
bets backend/internal/domain/bets/service.go Patrick
markets backend/internal/domain/markets/errors.go Patrick [ ]
markets backend/internal/domain/markets/models.go Patrick [ ]
markets backend/internal/domain/markets/service_details_test.go Patrick [ ]
markets backend/internal/domain/markets/service_listbystatus_test.go Patrick
markets backend/internal/domain/markets/service_marketbets_test.go Patrick [ ]
markets backend/internal/domain/markets/service_probability_test.go Patrick [ ]
markets backend/internal/domain/markets/service_resolve_test.go Patrick [ ]
markets backend/internal/domain/markets/service_search_test.go Patrick [ ]
markets backend/internal/domain/markets/service.go Patrick
math backend/internal/domain/math/market/dust_test.go Patrick [ ]
math backend/internal/domain/math/market/dust.go Patrick
math backend/internal/domain/math/market/marketvolume_test.go Patrick [ ]
math backend/internal/domain/math/market/marketvolume.go Patrick
math backend/internal/domain/math/outcomes/dbpm/marketshares_test.go Patrick [ ]
math backend/internal/domain/math/outcomes/dbpm/marketshares.go Patrick
math backend/internal/domain/math/positions/adjust_valuation_test.go Patrick [ ]
math backend/internal/domain/math/positions/adjust_valuation.go Patrick [ ]
math backend/internal/domain/math/positions/positionsmath_test.go Patrick
math backend/internal/domain/math/positions/positionsmath.go Patrick
math backend/internal/domain/math/positions/profitability_test.go Patrick [ ]
math backend/internal/domain/math/positions/profitability.go Patrick [ ]
math backend/internal/domain/math/positions/valuation_test.go Patrick [ ]
math backend/internal/domain/math/positions/valuation.go Patrick
math backend/internal/domain/math/probabilities/wpam/wpam_current_test.go Patrick
math backend/internal/domain/math/probabilities/wpam/wpam_current.go Patrick
math backend/internal/domain/math/probabilities/wpam/wpam_marketprobabilities_test.go Patrick
math backend/internal/domain/math/probabilities/wpam/wpam_marketprobabilities.go Patrick
users backend/internal/domain/users/errors.go Osnat [ ]
users backend/internal/domain/users/models.go Osnat [ ]
users backend/internal/domain/users/service_profile_test.go Osnat [ ]
users backend/internal/domain/users/service_transactions_test.go Osnat
users backend/internal/domain/users/service.go Osnat [ ]
users backend/internal/domain/users/transactions.go Osnat [ ]

@pwdel
Copy link
Member Author

pwdel commented Jan 4, 2026

OCP

  • See relevant PR(s) here
Area File Assignee Status
analytics backend/internal/domain/analytics/financialsnapshot_test.go Patrick [ ]
analytics backend/internal/domain/analytics/leaderboard_test.go Patrick [ ]
analytics backend/internal/domain/analytics/models.go Patrick [ ]
analytics backend/internal/domain/analytics/repository.go Patrick
analytics backend/internal/domain/analytics/service.go Patrick
analytics backend/internal/domain/analytics/systemmetrics_integration_test.go Patrick [ ]
analytics backend/internal/domain/analytics/systemmetrics_test.go Patrick [ ]
bets backend/internal/domain/bets/errors.go Patrick [ ]
bets backend/internal/domain/bets/models.go Patrick [ ]
bets backend/internal/domain/bets/service_helpers_test.go Patrick [ ]
bets backend/internal/domain/bets/service_test.go Patrick [ ]
bets backend/internal/domain/bets/service.go Patrick
markets backend/internal/domain/markets/errors.go Patrick [ ]
markets backend/internal/domain/markets/models.go Patrick [ ]
markets backend/internal/domain/markets/service_details_test.go Patrick [ ]
markets backend/internal/domain/markets/service_listbystatus_test.go Patrick [ ]
markets backend/internal/domain/markets/service_marketbets_test.go Patrick [ ]
markets backend/internal/domain/markets/service_probability_test.go Patrick [ ]
markets backend/internal/domain/markets/service_resolve_test.go Patrick [ ]
markets backend/internal/domain/markets/service_search_test.go Patrick [ ]
markets backend/internal/domain/markets/service.go Patrick
math backend/internal/domain/math/market/dust_test.go Patrick [ ]
math backend/internal/domain/math/market/dust.go Patrick
math backend/internal/domain/math/market/marketvolume_test.go Patrick [ ]
math backend/internal/domain/math/market/marketvolume.go Patrick
math backend/internal/domain/math/outcomes/dbpm/marketshares_test.go Patrick [ ]
math backend/internal/domain/math/outcomes/dbpm/marketshares.go Patrick [ ]
math backend/internal/domain/math/positions/adjust_valuation_test.go Patrick [ ]
math backend/internal/domain/math/positions/adjust_valuation.go Patrick [ ]
math backend/internal/domain/math/positions/positionsmath_test.go Patrick [ ]
math backend/internal/domain/math/positions/positionsmath.go Patrick
math backend/internal/domain/math/positions/profitability_test.go Patrick [ ]
math backend/internal/domain/math/positions/profitability.go Patrick [ ]
math backend/internal/domain/math/positions/valuation_test.go Patrick [ ]
math backend/internal/domain/math/positions/valuation.go Patrick [ ]
math backend/internal/domain/math/probabilities/wpam/wpam_current_test.go Patrick [ ]
math backend/internal/domain/math/probabilities/wpam/wpam_current.go Patrick [ ]
math backend/internal/domain/math/probabilities/wpam/wpam_marketprobabilities_test.go Patrick [ ]
math backend/internal/domain/math/probabilities/wpam/wpam_marketprobabilities.go Patrick [ ]
users backend/internal/domain/users/errors.go Osnat [ ]
users backend/internal/domain/users/models.go Osnat [ ]
users backend/internal/domain/users/service_profile_test.go Osnat [ ]
users backend/internal/domain/users/service_transactions_test.go Osnat [ ]
users backend/internal/domain/users/service.go Osnat [ ]
users backend/internal/domain/users/transactions.go Osnat [ ]

@pwdel
Copy link
Member Author

pwdel commented Jan 4, 2026

LSP

Area File Assignee Status
analytics backend/internal/domain/analytics/financialsnapshot_test.go Patrick [ ]
analytics backend/internal/domain/analytics/leaderboard_test.go Patrick [ ]
analytics backend/internal/domain/analytics/models.go Patrick [ ]
analytics backend/internal/domain/analytics/repository.go Patrick [ ]
analytics backend/internal/domain/analytics/service.go Patrick [ ]
analytics backend/internal/domain/analytics/systemmetrics_integration_test.go Patrick [ ]
analytics backend/internal/domain/analytics/systemmetrics_test.go Patrick [ ]
bets backend/internal/domain/bets/errors.go Patrick [ ]
bets backend/internal/domain/bets/models.go Patrick [ ]
bets backend/internal/domain/bets/service_helpers_test.go Patrick [ ]
bets backend/internal/domain/bets/service_test.go Patrick [ ]
bets backend/internal/domain/bets/service.go Patrick [ ]
markets backend/internal/domain/markets/errors.go Patrick [ ]
markets backend/internal/domain/markets/models.go Patrick [ ]
markets backend/internal/domain/markets/service_details_test.go Patrick [ ]
markets backend/internal/domain/markets/service_listbystatus_test.go Patrick [ ]
markets backend/internal/domain/markets/service_marketbets_test.go Patrick [ ]
markets backend/internal/domain/markets/service_probability_test.go Patrick [ ]
markets backend/internal/domain/markets/service_resolve_test.go Patrick [ ]
markets backend/internal/domain/markets/service_search_test.go Patrick [ ]
markets backend/internal/domain/markets/service.go Patrick [ ]
math backend/internal/domain/math/market/dust_test.go Patrick [ ]
math backend/internal/domain/math/market/dust.go Patrick [ ]
math backend/internal/domain/math/market/marketvolume_test.go Patrick [ ]
math backend/internal/domain/math/market/marketvolume.go Patrick [ ]
math backend/internal/domain/math/outcomes/dbpm/marketshares_test.go Patrick [ ]
math backend/internal/domain/math/outcomes/dbpm/marketshares.go Patrick [ ]
math backend/internal/domain/math/positions/adjust_valuation_test.go Patrick [ ]
math backend/internal/domain/math/positions/adjust_valuation.go Patrick [ ]
math backend/internal/domain/math/positions/positionsmath_test.go Patrick [ ]
math backend/internal/domain/math/positions/positionsmath.go Patrick [ ]
math backend/internal/domain/math/positions/profitability_test.go Patrick [ ]
math backend/internal/domain/math/positions/profitability.go Patrick [ ]
math backend/internal/domain/math/positions/valuation_test.go Patrick [ ]
math backend/internal/domain/math/positions/valuation.go Patrick [ ]
math backend/internal/domain/math/probabilities/wpam/wpam_current_test.go Patrick [ ]
math backend/internal/domain/math/probabilities/wpam/wpam_current.go Patrick [ ]
math backend/internal/domain/math/probabilities/wpam/wpam_marketprobabilities_test.go Patrick [ ]
math backend/internal/domain/math/probabilities/wpam/wpam_marketprobabilities.go Patrick [ ]
users backend/internal/domain/users/errors.go Osnat [ ]
users backend/internal/domain/users/models.go Osnat [ ]
users backend/internal/domain/users/service_profile_test.go Osnat [ ]
users backend/internal/domain/users/service_transactions_test.go Osnat [ ]
users backend/internal/domain/users/service.go Osnat [ ]
users backend/internal/domain/users/transactions.go Osnat [ ]

@pwdel
Copy link
Member Author

pwdel commented Jan 4, 2026

ISP

Area File Assignee Status
analytics backend/internal/domain/analytics/financialsnapshot_test.go Patrick [ ]
analytics backend/internal/domain/analytics/leaderboard_test.go Patrick [ ]
analytics backend/internal/domain/analytics/models.go Patrick [ ]
analytics backend/internal/domain/analytics/repository.go Patrick [ ]
analytics backend/internal/domain/analytics/service.go Patrick [ ]
analytics backend/internal/domain/analytics/systemmetrics_integration_test.go Patrick [ ]
analytics backend/internal/domain/analytics/systemmetrics_test.go Patrick [ ]
bets backend/internal/domain/bets/errors.go Patrick [ ]
bets backend/internal/domain/bets/models.go Patrick [ ]
bets backend/internal/domain/bets/service_helpers_test.go Patrick [ ]
bets backend/internal/domain/bets/service_test.go Patrick [ ]
bets backend/internal/domain/bets/service.go Patrick [ ]
markets backend/internal/domain/markets/errors.go Patrick [ ]
markets backend/internal/domain/markets/models.go Patrick [ ]
markets backend/internal/domain/markets/service_details_test.go Patrick [ ]
markets backend/internal/domain/markets/service_listbystatus_test.go Patrick [ ]
markets backend/internal/domain/markets/service_marketbets_test.go Patrick [ ]
markets backend/internal/domain/markets/service_probability_test.go Patrick [ ]
markets backend/internal/domain/markets/service_resolve_test.go Patrick [ ]
markets backend/internal/domain/markets/service_search_test.go Patrick [ ]
markets backend/internal/domain/markets/service.go Patrick [ ]
math backend/internal/domain/math/market/dust_test.go Patrick [ ]
math backend/internal/domain/math/market/dust.go Patrick [ ]
math backend/internal/domain/math/market/marketvolume_test.go Patrick [ ]
math backend/internal/domain/math/market/marketvolume.go Patrick [ ]
math backend/internal/domain/math/outcomes/dbpm/marketshares_test.go Patrick [ ]
math backend/internal/domain/math/outcomes/dbpm/marketshares.go Patrick [ ]
math backend/internal/domain/math/positions/adjust_valuation_test.go Patrick [ ]
math backend/internal/domain/math/positions/adjust_valuation.go Patrick [ ]
math backend/internal/domain/math/positions/positionsmath_test.go Patrick [ ]
math backend/internal/domain/math/positions/positionsmath.go Patrick [ ]
math backend/internal/domain/math/positions/profitability_test.go Patrick [ ]
math backend/internal/domain/math/positions/profitability.go Patrick [ ]
math backend/internal/domain/math/positions/valuation_test.go Patrick [ ]
math backend/internal/domain/math/positions/valuation.go Patrick [ ]
math backend/internal/domain/math/probabilities/wpam/wpam_current_test.go Patrick [ ]
math backend/internal/domain/math/probabilities/wpam/wpam_current.go Patrick [ ]
math backend/internal/domain/math/probabilities/wpam/wpam_marketprobabilities_test.go Patrick [ ]
math backend/internal/domain/math/probabilities/wpam/wpam_marketprobabilities.go Patrick [ ]
users backend/internal/domain/users/errors.go Osnat [ ]
users backend/internal/domain/users/models.go Osnat [ ]
users backend/internal/domain/users/service_profile_test.go Osnat [ ]
users backend/internal/domain/users/service_transactions_test.go Osnat [ ]
users backend/internal/domain/users/service.go Osnat [ ]
users backend/internal/domain/users/transactions.go Osnat [ ]

@pwdel
Copy link
Member Author

pwdel commented Jan 4, 2026

DIP

Area File Assignee Status
analytics backend/internal/domain/analytics/financialsnapshot_test.go Patrick [ ]
analytics backend/internal/domain/analytics/leaderboard_test.go Patrick [ ]
analytics backend/internal/domain/analytics/models.go Patrick [ ]
analytics backend/internal/domain/analytics/repository.go Patrick [ ]
analytics backend/internal/domain/analytics/service.go Patrick [ ]
analytics backend/internal/domain/analytics/systemmetrics_integration_test.go Patrick [ ]
analytics backend/internal/domain/analytics/systemmetrics_test.go Patrick [ ]
bets backend/internal/domain/bets/errors.go Patrick [ ]
bets backend/internal/domain/bets/models.go Patrick [ ]
bets backend/internal/domain/bets/service_helpers_test.go Patrick [ ]
bets backend/internal/domain/bets/service_test.go Patrick [ ]
bets backend/internal/domain/bets/service.go Patrick [ ]
markets backend/internal/domain/markets/errors.go Patrick [ ]
markets backend/internal/domain/markets/models.go Patrick [ ]
markets backend/internal/domain/markets/service_details_test.go Patrick [ ]
markets backend/internal/domain/markets/service_listbystatus_test.go Patrick [ ]
markets backend/internal/domain/markets/service_marketbets_test.go Patrick [ ]
markets backend/internal/domain/markets/service_probability_test.go Patrick [ ]
markets backend/internal/domain/markets/service_resolve_test.go Patrick [ ]
markets backend/internal/domain/markets/service_search_test.go Patrick [ ]
markets backend/internal/domain/markets/service.go Patrick [ ]
math backend/internal/domain/math/market/dust_test.go Patrick [ ]
math backend/internal/domain/math/market/dust.go Patrick [ ]
math backend/internal/domain/math/market/marketvolume_test.go Patrick [ ]
math backend/internal/domain/math/market/marketvolume.go Patrick [ ]
math backend/internal/domain/math/outcomes/dbpm/marketshares_test.go Patrick [ ]
math backend/internal/domain/math/outcomes/dbpm/marketshares.go Patrick [ ]
math backend/internal/domain/math/positions/adjust_valuation_test.go Patrick [ ]
math backend/internal/domain/math/positions/adjust_valuation.go Patrick [ ]
math backend/internal/domain/math/positions/positionsmath_test.go Patrick [ ]
math backend/internal/domain/math/positions/positionsmath.go Patrick [ ]
math backend/internal/domain/math/positions/profitability_test.go Patrick [ ]
math backend/internal/domain/math/positions/profitability.go Patrick [ ]
math backend/internal/domain/math/positions/valuation_test.go Patrick [ ]
math backend/internal/domain/math/positions/valuation.go Patrick [ ]
math backend/internal/domain/math/probabilities/wpam/wpam_current_test.go Patrick [ ]
math backend/internal/domain/math/probabilities/wpam/wpam_current.go Patrick [ ]
math backend/internal/domain/math/probabilities/wpam/wpam_marketprobabilities_test.go Patrick [ ]
math backend/internal/domain/math/probabilities/wpam/wpam_marketprobabilities.go Patrick [ ]
users backend/internal/domain/users/errors.go Osnat [ ]
users backend/internal/domain/users/models.go Osnat [ ]
users backend/internal/domain/users/service_profile_test.go Osnat [ ]
users backend/internal/domain/users/service_transactions_test.go Osnat [ ]
users backend/internal/domain/users/service.go Osnat [ ]
users backend/internal/domain/users/transactions.go Osnat [ ]

@astrosnat
Copy link
Collaborator

astrosnat commented Jan 5, 2026

I have a question about /users/errors.go

It's very short

var (

	ErrUserNotFound           = errors.New("user not found")

	ErrUserAlreadyExists      = errors.New("user already exists")

	ErrInvalidCredentials     = errors.New("invalid credentials")

	ErrInsufficientBalance    = errors.New("insufficient balance")

	ErrInvalidUserData        = errors.New("invalid user data")

	ErrUnauthorized           = errors.New("unauthorized")

	ErrInvalidTransactionType = errors.New("invalid transaction type")

)

Is this violating SRP because different types of errors are combined into the same variable, or is it OK because it's one big "error" type that belongs to the users package?

Similarly for /users/transactions.go it's 5 constants that all seem to be tied to the same general concept of different transaction fees

* SRP for bets, markets and math.

* Updating with passed tests.

* Updating SRP for financials, leaderboard, analytics

* Simplifying positionsmath, valuation, wpam

* SRP on selldusst calculator.

---------

Co-authored-by: Patrick W. Delaney <[email protected]>
@pwdel
Copy link
Member Author

pwdel commented Jan 5, 2026

@astrosnat

I have a question about /users/errors.go

It's very short

var (

	ErrUserNotFound           = errors.New("user not found")

	ErrUserAlreadyExists      = errors.New("user already exists")

	ErrInvalidCredentials     = errors.New("invalid credentials")

	ErrInsufficientBalance    = errors.New("insufficient balance")

	ErrInvalidUserData        = errors.New("invalid user data")

	ErrUnauthorized           = errors.New("unauthorized")

	ErrInvalidTransactionType = errors.New("invalid transaction type")

)

Is this violating SRP because different types of errors are combined into the same variable, or is it OK because it's one big "error" type that belongs to the users package?

Similarly for /users/transactions.go it's 5 constants that all seem to be tied to the same general concept of different transaction fees

In Go, it’s common (and encouraged) to have a structure like this:

errors.go = exported sentinel errors for the package
service.go / repo.go etc.

Go documentation typically doesn’t frame guidance in SOLID terms; it frames it as:

  • readability/clarity,
  • small, focused packages,
  • composition over inheritance,
  • small interfaces and decoupling via interfaces.

That said, there’s overlap in spirit with SRP, small packages/files by responsibility ... errors.go is “one reason to change: the package error contract”.

Hypothetically if someone changes this errors.go, there will be one reason for it, basically the user contract changed. So while there are multiple things going on in this one file, it's all centered around the, "thing," ... user. Some of this is opinionated. You could hypothetically make a file for each one of those errors to make it ultra-super SRP but I think that's over-engineering.

@astrosnat
Copy link
Collaborator

@pwdel thanks, just wanted to check with you - errors.go and transactions.go both seemed sufficiently small to me that I was concerned splitting them would be making more work for not much gain.

I'll focus on service.go and the associated tests

pwdel and others added 4 commits January 5, 2026 13:21
* Updating OCP for bets

* Refactoring markets for OCP

* Expanded dust.go to allow custom sell dust policies, include ProbabilityProvider, PayoutModel, PositionCalculator to encapsulate strategies, existing APIs now delegate through calculator, default WPAM-DBPM implementations wired for new interfaces.

* Added MarketPositionCalculator interface with WPAM DBPM implementation, wired strategy injection into both analytics Service, WithPositionCalculator option, and GormRepositoroy, default calculator, nil-safety, Refactored leaderboard data loading

---------

Co-authored-by: Patrick W. Delaney <[email protected]>
* Exported sale calculation data and switched to SaleCalculator.Calculate to return it, updating Sell flow, added interface conformance assertions for sergice and hardened validator dependency handling so alternate implemntnations cant trigger panics or rely on stronger preconditions.

* Applying liskov substitution. Default clock implementation, guard repo fetches against nil markets and invalid ids, normalize search and overview helpers to handle nil slices.

* Removed panic on missing seeds, getSeeds now supplies and defaults, PositionCalculator toleratoes nil dependencies by falling back to default probability / payout models, preventing panics from zero value calculators.

* Added LSP safeguards, service entrypoints against missing dependencies, ComputeUserFinancials and ComputeSystemMetrics, service construction seeds default strategies via ensureStrategyDefaults, interface assertions, Gorm repo methods now fail fast with descriptive error.

---------

Co-authored-by: Patrick W. Delaney <[email protected]>
…sitory, MarketBetRepository and update resolution policy and helpers. (#613)

Co-authored-by: Patrick W. Delaney <[email protected]>
* Updating.

* Updating.

* Updating.

---------

Co-authored-by: Patrick W. Delaney <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants