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 pwdel marked this pull request as ready for review November 13, 2025 23:20
@astrosnat
Copy link
Collaborator

astrosnat commented Nov 14, 2025

image when trying to stop containers on development, I get this...? has this happened to anyone else...?

update: sudo systemctl restart docker.service docker.socket fixes it

@astrosnat
Copy link
Collaborator

testing on localhost...
image
3 significant figures, not 2, but the important part is this works

image this is an unclear error message when trying to bet on a resolved market, but this is a nitpick

overall, looks good to me, happy to approve if you have tested this on kconfs @pwdel

@pwdel
Copy link
Member Author

pwdel commented Nov 14, 2025

image when trying to stop containers on development, I get this...? has this happened to anyone else...?
update: sudo systemctl restart docker.service docker.socket fixes it

No, I have never seen this. Odd. @ntoufoudis do you get this on the branch we're discussing?

@ntoufoudis
Copy link
Collaborator

image when trying to stop containers on development, I get this...? has this happened to anyone else...?

update: sudo systemctl restart docker.service docker.socket fixes it

No I don't get this. Permission denied usually has to do with privileges, running as root or not. If docker requires root to start and stop containers then this makes sense. You should try running it with sudo.

@pwdel pwdel force-pushed the fix/checkpoint20251020-80 branch from 63e91a3 to 663b137 Compare November 19, 2025 01:40
@raisch
Copy link
Collaborator

raisch commented Nov 21, 2025

There so much here, rather than producing a full review, I'll make some general comments here.

When I review another engineer's work, I try to look at everything as holistically as I can, using standard development Best Practices as my polestar.

I look specifically for things like

  • How complex the code is ("cyclomatic complexity") which makes the code difficult to reason about and maintain.
  • Violations of the Open/Closed Principle that adversely affect maintainability but also tend to make code very brittle, such that a small change in one place has unattended affect in other areas of the codebase. (This is also known as "coupling", as in how we strive for "low coupling/high cohesion".)
  • Violations of the Single Responsibility Principle which cause all of the above issues, but I think most importantly, makes it very hard to reason about program flow. (I have a whole lecture I give about how our minds can only deal with 7 - 9 things at any one time due to how we process short-term vs. long-term memory.)

The goal of this evaluation is to help make the codebase:

  • Reliable
  • Predictable
  • Easy to reason about
  • Easier to modify without unexpected consequences
  • Easier to maintain by a team.

Note that of the worst offenders listed below, most of the files/functions appear in multiple categories.

Cyclomatic Complexity

An old but I feel still pertinent measure of how many paths there are out of a function/method/etc. I view anything above 8 to be unnecessarily complex. (In my own work, I strive for 4 or less.)

Worst offenders:

  • internal/domain/analytics/service.go (line 241) ComputeGlobalLeaderboard has cyclomatic 20: pulls markets/bets, calculates user aggregates, determines earliest bets, and sorts/ranks in one method. Splitting data access, aggregation, and ranking into helpers would satisfy SRP and bring complexity under the ≤8 target.
  • internal/domain/analytics/service.go (line 89) ComputeSystemMetrics (cyclomatic 14) performs debt arithmetic, market fee math, participation fee counting, and response construction in a single block while issuing N+1 market bet queries. Extract per-concern calculators (user debt, active volume, fee tallies) and encapsulate repository calls to reduce complexity and improve testability.
  • handlers/markets/searchmarkets.go (line 15) SearchMarketsHandler (cyclomatic 19) mixes HTTP method checks, query parsing, validation/sanitization, pagination defaults, domain calls, DTO mapping, and response encoding. A request parser + responder layer would lower complexity and align with Single Responsibility.
  • handlers/markets/listmarkets.go (line 14) ListMarketsHandlerFactory (cyclomatic 15) similarly combines parameter parsing, branching between services, error mapping, DTO building, and encoding. Extracting pagination/status parsing and response writing into dedicated helpers/middleware would shrink the handler’s complexity.
  • internal/domain/markets/service.go (line 370) SearchMarkets and (line 444) ResolveMarket (cyclomatic 15/16) both bundle validation, fallback search logic, and payout/refund workflows. Consider moving payout transaction loops to a transaction-oriented collaborator and isolating fallback-search assembly to dedicated functions to hit the complexity ≤8 goal and keep domain operations single-purpose.
  • util/postgres.go (line 15) InitDB (cyclomatic 10) owns env resolution, DSN construction, connection open, and global mutation, and aborts the process on failure. Returning an error and encapsulating config parsing separately would drop complexity and better respect SOLID (no hidden global state).

Single Responsibility Principle

"A function/module/etc. should only have one reason to change."

The example I use to describe this is a function that retrieves data from some source, transforms it into printable form, and sends it to a printer.

In this function, there are many "reasons to change": if

  • the function needs to pull its data from somewhere else
  • the transformation (formatting) needs to be updated
  • the result needs to be sent to the file system, external url, etc. rather than the printer

Three reasons to change, in one function. (This is related to Open/Closed below.)

There are lots of violations, but the worst offenders are:

  • internal/domain/analytics/service.go (line 241) ComputeGlobalLeaderboard — data fetching, per-market aggregation, per-user aggregation, earliest-bet detection, sorting, and ranking all in one routine.
  • internal/domain/analytics/service.go (line 89) ComputeSystemMetrics — mixes config access, user debt math, market fee/volume tallying, participation fee detection, and response assembly plus multiple repo queries.
  • handlers/markets/searchmarkets.go (line 15) SearchMarketsHandler — HTTP method gate, query parsing, validation/sanitization, paging defaults, domain calls, DTO mapping, and JSON encoding in one function.
  • handlers/markets/listmarkets.go (line 14) ListMarketsHandlerFactory — parameter parsing, branching service calls, error mapping, DTO building, and response writing combined.
  • internal/domain/markets/service.go (line 444) ResolveMarket — input validation, auth checks, repo updates, and separate refund/payout transaction loops bundled together.

Open/Closed Principle

"A function/module/etc. should be open to extension but closed to modification."

Any time you need to go back into the code to fix a bug, without strict adherence to this principle, you will cause more bugs to pop up based on your modifications to existing code. (This is another instance of "coupling".)

The best way to ameliorate this problem is to "lock" existing work and use extension to apply modifications.

The worst offenders:

  • internal/domain/analytics/service.go (line 89) ComputeSystemMetrics — tightly bundles debt, fee, and volume calculations with data access; any new fee type or adjustment requires modifying this method rather than extending via strategy/helpers.
  • internal/domain/analytics/service.go (line 241) ComputeGlobalLeaderboard — aggregation, earliest-bet detection, and ranking are hard-coded; adding a new ranking dimension or tie-breaker forces editing the core function instead of composing strategies.
  • handlers/markets/searchmarkets.go (line 15) SearchMarketsHandler — request parsing, validation/sanitization, domain call, and response formatting are intertwined; adding new filters or response fields means touching this handler rather than plugging in middleware or mappers.
  • handlers/markets/listmarkets.go (line 14) ListMarketsHandlerFactory — same pattern: branching logic and DTO assembly live in one place, so new list variants/status handling require edits instead of extension.
  • internal/domain/markets/service.go (line 370) SearchMarkets — fallback search logic and filtering rules are embedded; changing thresholds, statuses, or fallback strategy demands direct modification.
  • internal/domain/markets/service.go (line 444) ResolveMarket — payout/refund pathways are inlined; adding new resolutions or payout rules requires changing this method rather than supplying pluggable settlement strategies.

Other Violations of SOLID Principles

  • util/postgres.go (line 15) InitDB — violates SRP and Dependency Inversion: hides configuration parsing, connection building, and global state mutation; callers can’t inject DB, forcing hard coupling and fatal exit paths.
  • internal/service/auth/loggin.go (line 28) LoginHandler — high coupling and DIP/LSP issues: directly depends on global DB via util.GetDB and constructs SecurityService inline; no interface boundaries make swapping storage/auth strategies or testing difficult.
  • internal/domain/analytics/service.go (repo field is a concrete interface but still) — hidden N+1 queries in ComputeSystemMetrics/ComputeGlobalLeaderboard break Interface Segregation/Dependency Inversion because Repository forces both ordered and per-market bet calls; no separate query abstractions for bulk operations.
  • handlers/markets/searchmarkets.go and handlers/markets/listmarkets.go — handlers depend on concrete svc behaviors and perform validation/sanitization/mapping themselves, mixing concerns and resisting Open/Closed; lack of small interfaces for parsing/response writing impedes substitution/testing.
  • internal/domain/markets/service.go (line 444) ResolveMarket — violates SRP/OCP: service controls auth, settlement state, repo updates, and user transactions directly; no strategy or collaborator to extend payout/refund logic without modifying the method.
  • security/sanitizer.go (line 292) isValidEmoji — LSP/ISP risk: “sanitizer” both validates and parses; callers can’t substitute a different emoji/allow-list without reworking the sanitizer, and the function accepts any ASCII, weakening contract clarity.

Comments on Coupling/Cohesion

Coupling is how strongly one module depends on another’s details. High coupling means changes in one piece force changes elsewhere (shared state, concrete types, hidden assumptions); low coupling keeps modules talking through stable abstractions so they can evolve independently.

Cohesion is how tightly the responsibilities within a module relate to a single purpose. High cohesion means the code in a unit works toward one focused task; low cohesion means a grab bag of unrelated duties living together, making the unit harder to understand, test, and change.

Good engineers alway strive for Low Coupling and High Cohesion.

@pwdel
Copy link
Member Author

pwdel commented Dec 3, 2025

Working on Cyclomatic Complexity. I found that there's a Go tool for this. Here's my current report for 5 or more.

% gocyclo --over 4 .
20 security isValidEmoji security/sanitizer.go:293:1
19 analytics_test TestComputeSystemMetrics_BalancedAfterFinalLockedBet internal/domain/analytics/systemmetrics_integration_test.go:14:1
19 positions TestMarketPositionsHandlerWithService_IncludesZeroPositionUsers handlers/positions/positionshandler_test.go:103:1
19 marketshandlers SearchMarketsHandler handlers/markets/searchmarkets.go:15:1
18 marketpublicresponse TestGetPublicResponseMarketMapping handlers/marketpublicresponse/publicresponsemarket_test.go:86:1
16 markets (*Service).ResolveMarket internal/domain/markets/service.go:444:1
15 seed TestSeedHomepage_RendersHTML seed/seed_test.go:12:1
15 seed TestSeedHomepage_Integration_WithActualFile seed/integration_test.go:13:1
15 markets (*Service).SearchMarkets internal/domain/markets/service.go:371:1
15 analytics (*GormRepository).UserMarketPositions internal/domain/analytics/repository.go:66:1
15 marketshandlers ListMarketsHandlerFactory handlers/markets/listmarkets.go:14:1
15 betshandlers TestMarketBetsHandlerWithService handlers/bets/betshandler_test.go:72:1
14 users_test TestServiceGetUserPortfolio internal/domain/users/service_transactions_test.go:103:1
14 markets_test TestServiceGetMarketDetailsCalculatesMetrics internal/domain/markets/service_details_test.go:15:1
14 analytics_test TestResolveMarket_DistributesAllBetVolume internal/domain/analytics/systemmetrics_integration_test.go:119:1
14 analytics (*Service).ComputeSystemMetrics internal/domain/analytics/service.go:89:1
13 migration_test TestRun_AppliesInOrder_And_Persists migration/migrate_test.go:33:1
13 markets_test TestServiceSearchMarketsFiltersByStatus internal/domain/markets/service_search_test.go:73:1
13 markets (*Service).GetMarketLeaderboard internal/domain/markets/service.go:579:1
13 usershandlers TestListUserMarketsReturnsDistinctMarketsOrderedByRecentBet handlers/users/listusers_test.go:16:1
13 marketshandlers (*Handler).MarketLeaderboard handlers/markets/handler.go:475:1
13 marketshandlers (*Handler).ListByStatus handlers/markets/handler.go:364:1
12 security validateStrongPassword security/validator.go:103:1
12 markets TestGormRepositoryListBetsForMarket internal/repository/markets/repository_test.go:84:1
12 markets (*Service).ProjectProbability internal/domain/markets/service.go:658:1
12 bets (*Service).Sell internal/domain/bets/service.go:138:1
12 bets (*Service).Place internal/domain/bets/service.go:69:1
12 usershandlers TestUserMarketPositionHandlerReturnsUserPosition handlers/users/userpositiononmarkethandler_test.go:21:1
12 marketshandlers (*Handler).SearchMarkets handlers/markets/handler.go:235:1
11 markets_test TestGetMarketBets_ReturnsProbabilities internal/domain/markets/service_marketbets_test.go:84:1
11 markets (*Service).CreateMarket internal/domain/markets/service.go:141:1
11 bets_test TestServiceSell_Succeeds internal/domain/bets/service_test.go:175:1
11 analytics TestComputeSystemMetrics_WithData internal/domain/analytics/systemmetrics_test.go:31:1
11 analytics TestComputeGlobalLeaderboard_OrdersByProfit internal/domain/analytics/leaderboard_test.go:86:1
11 usershandlers TestEmojiValidation handlers/users/changeemoji_test.go:9:1
11 usershandlers TestDisplayNameValidation handlers/users/changedisplayname_test.go:9:1
11 marketshandlers TestSearchMarketsHandlerSuccess handlers/markets/searchmarkets_handler_test.go:94:1
10 util InitDB util/postgres.go:17:1
10 security getFieldErrorMessage security/validator.go:58:1
10 security TestValidateAndSanitizeBetInput security/security_test.go:263:1
10 security (*SecurityService).ValidateAndSanitizeUserInput security/security.go:56:1
10 migration Run migration/migrate.go:38:1
10 markets (*GormRepository).Search internal/repository/markets/repository.go:184:1
10 bets TestGormRepositoryCreateAndUserHasBet internal/repository/bets/repository_test.go:12:1
10 users_test TestServiceGetUserFinancials internal/domain/users/service_transactions_test.go:159:1
10 users_test TestServiceUpdatePersonalLinks internal/domain/users/service_profile_test.go:220:1
10 users (*Service).ChangePassword internal/domain/users/service.go:520:1
10 markets_test TestServiceListByStatusFiltersMarkets internal/domain/markets/service_listbystatus_test.go:60:1
10 usershandlers UserMarketPositionHandlerWithService handlers/users/userpositiononmarkethandler.go:17:1
10 usershandlers TestPersonalLinksValidation handlers/users/changepersonallinks_test.go:9:1
10 positions MarketUserPositionHandlerWithService handlers/positions/positionshandler.go:72:1
10 positions MarketPositionsHandlerWithService handlers/positions/positionshandler.go:15:1
10 marketshandlers (*Handler).ListMarkets handlers/markets/handler.go:171:1
10 marketshandlers TestMarketTitleValidation handlers/markets/createmarket_test_security.go:9:1
10 marketshandlers CreateMarketHandlerWithService handlers/markets/createmarket.go:159:1
10 marketshandlers (*CreateMarketService).Handle handlers/markets/createmarket.go:64:1
10 sellbetshandlers SellPositionHandler handlers/bets/selling/sellpositionhandler.go:16:1
10 adminhandlers AddUserHandler handlers/admin/adduser.go:19:1
9 seed TestSeedHomepage_FallbackContent seed/seed_test.go:103:1
9 security TestValidateAndSanitizeMarketInput security/security_test.go:190:1
9 security TestValidateAndSanitizeUserInput security/security_test.go:108:1
9 security (*Sanitizer).SanitizePersonalLink security/sanitizer.go:126:1
9 migration_test TestRun_IsIdempotent migration/migrate_test.go:91:1
9 auth LoginHandler internal/service/auth/loggin.go:28:1
9 users TestGormRepositoryListUserBets internal/repository/users/repository_test.go:67:1
9 markets TestGormRepositoryCreateAndGetByID internal/repository/markets/repository_test.go:14:1
9 markets (*GormRepository).ListByStatus internal/repository/markets/repository.go:144:1
9 markets (*GormRepository).List internal/repository/markets/repository.go:105:1
9 users (*Service).UpdatePersonalLinks internal/domain/users/service.go:412:1
9 positionsmath TestCalculateMarketPositions_IncludesZeroPositionUsers internal/domain/math/positions/positionsmath_test.go:94:1
9 positionsmath TestCalculateMarketPositions_WPAM_DBPM internal/domain/math/positions/positionsmath_test.go:10:1
9 markets (*Service).GetMarketBets internal/domain/markets/service.go:741:1
9 bets_test TestServicePlace_Succeeds internal/domain/bets/service_test.go:92:1
9 bets (*Service).calculateSale internal/domain/bets/service.go:231:1
9 analytics TestComputeUserFinancials_WithActivePositions internal/domain/analytics/financialsnapshot_test.go:78:1
9 usershandlers TestDescriptionValidation handlers/users/changedescription_test.go:9:1
9 setup TestGetSetupHandler handlers/setup/setuphandler_test.go:12:1
9 marketshandlers ResolveMarketHandler handlers/markets/resolvemarket.go:20:1
9 marketshandlers GetMarketsHandler handlers/markets/getmarkets.go:13:1
9 marketshandlers TestMarketDescriptionValidation handlers/markets/createmarket_test_security.go:86:1
9 http TestAdminUpdate_Success handlers/cms/homepage/http/handler_test.go:61:1
9 sellbetshandlers TestSellPositionHandler_Success handlers/bets/selling/sellpositionhandler_test.go:107:1
9 buybetshandlers PlaceBetHandler handlers/bets/buying/buypositionhandler.go:15:1
8 util TestGetEnvLoadsFile util/util_test.go:88:1
8 security TestValidationRules security/validator_test.go:133:1
8 security validateUsername security/validator.go:88:1
8 security getClientIP security/ratelimit.go:123:1
8 users_test TestServiceApplyTransaction internal/domain/users/service_transactions_test.go:21:1
8 users_test TestServiceUpdateDisplayName internal/domain/users/service_profile_test.go:165:1
8 users_test TestServiceUpdateDescription internal/domain/users/service_profile_test.go:136:1
8 users (*Service).GetUserPortfolio internal/domain/users/service.go:242:1
8 positionsmath CalculateMarketLeaderboard internal/domain/math/positions/profitability.go:23:1
8 positionsmath CalculateMarketPositions_WPAM_DBPM internal/domain/math/positions/positionsmath.go:45:1
8 positionsmath TestAdjustUserValuationsToMarketVolume internal/domain/math/positions/adjust_valuation_test.go:8:1
8 dbpm AggregateUserPayoutsDBPM internal/domain/math/outcomes/dbpm/marketshares.go:227:1
8 markets_test TestGetMarketPositions_ReturnsRepositoryData internal/domain/markets/service_marketbets_test.go:181:1
8 analytics (*Service).ComputeGlobalLeaderboard internal/domain/analytics/service.go:241:1
8 analytics TestComputeUserFinancials_NewUser_NoPositions internal/domain/analytics/financialsnapshot_test.go:22:1
8 app TestBuildApplicationWiresMarketsDependencies internal/app/container_test.go:11:1
8 usershandlers TestPasswordLengthValidation handlers/users/changepassword_test.go:204:1
8 usershandlers TestPasswordValidation handlers/users/changepassword_test.go:9:1
8 marketshandlers TestMarketLeaderboardHandler_Smoke handlers/markets/handler_status_leaderboard_test.go:69:1
8 marketshandlers (*Handler).ResolveMarket handlers/markets/handler.go:314:1
8 dto TestCreateMarketRequestJSONParsing handlers/markets/dto/dto_test.go:10:1
8 marketshandlers TestMarketInputXSSPrevention handlers/markets/createmarket_test_security.go:167:1
8 homepage TestService_UpdateHome_Markdown handlers/cms/homepage/service_test.go:107:1
8 homepage (*Service).UpdateHome handlers/cms/homepage/service.go:104:1
8 buybetshandlers TestPlaceBetHandler_Success handlers/bets/buying/buypositionhandler_test.go:113:1
8 betshandlers MarketBetsHandlerWithService handlers/bets/betshandler.go:15:1
7 util TestGetEnvMissingFile util/util_test.go:128:1
7 setup TestLoadEconomicsConfigSingleton setup/setup_test.go:5:1
7 security SecurityHeadersMiddleware security/headers.go:42:1
7 migrations_test TestMigrateAddMarketLabels_AddsColumnsAndBackfills migration/migrations/20251020_140500_add_market_labels_test.go:47:1
7 migrations MigrateAddMarketLabels migration/migrations/20251020_140500_add_market_labels.go:11:1
7 logging LogAnyType logging/loggingutils.go:27:1
7 logger TestCustomLogger_Info_CallerDepth_FileReported logger/simplelogging_test.go:91:1
7 logger TestLogInfo_Convenience_WritesExpectedParts logger/simplelogging_test.go:43:1
7 auth TestExtractTokenFromHeader internal/service/auth/middleware_test.go:35:1
7 auth ValidateTokenAndGetUser internal/service/auth/auth.go:37:1
7 users TestGormRepositoryGetByUsername internal/repository/users/repository_test.go:14:1
7 markets TestGormRepositoryUpdateLabels internal/repository/markets/repository_test.go:57:1
7 users_test TestServiceChangePassword internal/domain/users/service_profile_test.go:257:1
7 users_test TestServiceUpdateEmoji internal/domain/users/service_profile_test.go:194:1
7 users (*Service).UpdateEmoji internal/domain/users/service.go:382:1
7 users (*Service).UpdateDisplayName internal/domain/users/service.go:355:1
7 positionsmath CalculateRoundedUserValuationsFromUserMarketPositions internal/domain/math/positions/valuation.go:14:1
7 positionsmath DeterminePositionType internal/domain/math/positions/profitability.go:101:1
7 dbpm TestAggregateUserPayoutsDBPM internal/domain/math/outcomes/dbpm/marketshares_test.go:614:1
7 dbpm TestCalculateCoursePayoutsDBPM internal/domain/math/outcomes/dbpm/marketshares_test.go:127:1
7 markets (*Service).ValidateLabels internal/domain/markets/service.go:893:1
7 markets (*Service).validateCustomLabels internal/domain/markets/service.go:856:1
7 analytics (*Service).ComputeUserFinancials internal/domain/analytics/service.go:36:1
7 usershandlers GetUserFinancialHandler handlers/users/financial.go:13:1
7 usershandlers TestPersonalLinksLengthValidation handlers/users/changepersonallinks_test.go:132:1
7 usershandlers TestEmojiLengthValidation handlers/users/changeemoji_test.go:138:1
7 marketshandlers TestListByStatusHandler_Smoke handlers/markets/handler_status_leaderboard_test.go:17:1
7 dto TestSearchResponseJSON handlers/markets/dto/dto_test.go:88:1
7 marketshandlers TestValidateMarketResolutionTime handlers/markets/createmarket_test.go:56:1
6 util TestUniqueDisplayNameAndEmail util/util_test.go:61:1
6 security validateMarketID security/validator.go:160:1
6 security validatePositiveAmount security/validator.go:138:1
6 security TestDefaultRateLimitConfig security/ratelimit_test.go:228:1
6 models TestCreateBet models/bets_test.go:60:1
6 migrations init migration/migrations/20251013_080000_core_models.go:13:1
6 migration_test TestRun_PersistsAppliedAt migration/migrate_test.go:141:1
6 logger TestLogError_Convenience_IncludesError logger/simplelogging_test.go:67:1
6 auth TestCheckMustChangePasswordFlag internal/service/auth/middleware_test.go:130:1
6 users TestGormRepositoryUpdateBalance internal/repository/users/repository_test.go:40:1
6 users (*GormRepository).List internal/repository/users/repository.go:105:1
6 users_test TestServiceGetUserCredit internal/domain/users/service_transactions_test.go:73:1
6 users (*Service).UpdateDescription internal/domain/users/service.go:328:1
6 positionsmath TestCalculateRoundedUserValuationsFromUserMarketPositions internal/domain/math/positions/valuation_test.go:24:1
6 dbpm singleCreditYesNoAllocator internal/domain/math/outcomes/dbpm/marketshares.go:287:1
6 dbpm CalculateNormalizationFactorsDBPM internal/domain/math/outcomes/dbpm/marketshares.go:97:1
6 markets_test TestResolveMarketPaysWinners internal/domain/markets/service_resolve_test.go:131:1
6 markets (*Service).GetMarketDetails internal/domain/markets/service.go:278:1
6 analytics TestComputeSystemMetrics_EmptyDatabase internal/domain/analytics/systemmetrics_test.go:12:1
6 analytics findEarliestBetsPerUser internal/domain/analytics/service.go:347:1
6 publicuser TestGetPortfolioHandlerSuccess handlers/users/publicuser/portfolio_test.go:78:1
6 publicuser GetPortfolioHandler handlers/users/publicuser/portfolio.go:14:1
6 privateuser TestGetPrivateProfileUserResponse_Success handlers/users/privateuser/privateuser_test.go:13:1
6 dto TestPortfolioResponseJSONRoundTrip handlers/users/dto/dto_test.go:67:1
6 usercredit TestGetUserCreditHandlerSuccess handlers/users/credit/usercredit_test.go:81:1
6 usercredit GetUserCreditHandler handlers/users/credit/usercredit.go:14:1
6 usershandlers ChangePersonalLinksHandler handlers/users/changepersonallinks.go:13:1
6 usershandlers ChangePasswordHandler handlers/users/changepassword.go:14:1
6 usershandlers TestEmojiXSSPrevention handlers/users/changeemoji_test.go:95:1
6 usershandlers ChangeEmojiHandler handlers/users/changeemoji.go:13:1
6 usershandlers TestDisplayNameXSSPrevention handlers/users/changedisplayname_test.go:77:1
6 usershandlers ChangeDisplayNameHandler handlers/users/changedisplayname.go:13:1
6 usershandlers TestDescriptionXSSPrevention handlers/users/changedescription_test.go:81:1
6 usershandlers ChangeDescriptionHandler handlers/users/changedescription.go:13:1
6 marketshandlers extractUsernameFromRequest handlers/markets/resolvemarket.go:72:1
6 marketshandlers ProjectNewProbabilityHandler handlers/markets/marketprojectedprobability.go:15:1
6 marketshandlers (*Handler).handleError handlers/markets/handler.go:608:1
6 marketshandlers (*Handler).UpdateLabels handlers/markets/handler.go:97:1
6 marketshandlers (*Handler).CreateMarket handlers/markets/handler.go:45:1
6 dto TestMarketOverviewResponseJSONRoundTrip handlers/markets/dto/dto_test.go:36:1
6 marketshandlers TestMarketInputSanitization handlers/markets/createmarket_test_security.go:307:1
6 betshandlers TestBetOutcomeValidation handlers/bets/placebethandler_test_security.go:300:1
6 betshandlers TestBetInputSanitization handlers/bets/placebethandler_test_security.go:105:1
6 adminhandlers TestUsernameLengthValidation handlers/admin/adduser_test.go:243:1
6 adminhandlers TestUsernameSanitization handlers/admin/adduser_test.go:100:1
6 errors TestHandleHTTPError errors/httperror_test.go:13:1
5 util TestGenerateUniqueApiKey util/util_test.go:11:1
5 setuptesting TestBuildInitialMarketAppConfig setup/setuptesting/setuptesting_test.go:7:1
5 setup TestChartSigFigsClamping setup/setup_test.go:32:1
5 setup ChartSigFigs setup/setup.go:108:1
5 server getBoolEnv server/server.go:56:1
5 server getListEnv server/server.go:37:1
5 seed TestSeedHomepage_DoesNotDuplicateExisting seed/seed_test.go:151:1
5 seed SeedUsers seed/seed.go:24:1
5 security TestNewSecurityService security/security_test.go:9:1
5 security TestRateLimitManager security/ratelimit_test.go:185:1
5 security TestGetClientIP security/ratelimit_test.go:131:1
5 security TestLoginRateLimitMiddleware security/ratelimit_test.go:80:1
5 migrations_test TestCoreModelsMigration_CreatesTablesAndColumns migration/migrations/20251013_080000_core_models_test.go:10:1
5 main main main.go:15:1
5 auth TestParseToken internal/service/auth/middleware_test.go:86:1
5 users_test TestServiceGetPrivateProfile internal/domain/users/service_profile_test.go:299:1
5 users (*Service).GetUserFinancials internal/domain/users/service.go:303:1
5 wpam_test TestCalculateMarketProbabilitiesWPAM internal/domain/math/probabilities/wpam/wpam_marketprobabilities_test.go:146:1
5 positionsmath GetEarliestBetTime internal/domain/math/positions/profitability.go:83:1
5 dbpm adjustForNegativeExcess internal/domain/math/outcomes/dbpm/marketshares.go:185:1
5 dbpm adjustForPositiveExcess internal/domain/math/outcomes/dbpm/marketshares.go:157:1
5 markets_test TestResolveMarketRefundsOnNA internal/domain/markets/service_resolve_test.go:101:1
5 markets_test TestProjectProbability_ComputesProjection internal/domain/markets/service_probability_test.go:62:1
5 markets (*Service).GetMarketPositions internal/domain/markets/service.go:800:1
5 markets (*Service).ListByStatus internal/domain/markets/service.go:555:1
5 markets countUniqueUsers internal/domain/markets/service.go:354:1
5 bets sharesOwnedForOutcome internal/domain/bets/service.go:214:1
5 analytics aggregateLeaderboardUserStats internal/domain/analytics/service.go:321:1
5 analytics TestComputeUserFinancials_NegativeBalance internal/domain/analytics/financialsnapshot_test.go:51:1
5 usershandlers TestGetPublicUserHandlerReturnsPublicUser handlers/users/publicuser_test.go:74:1
5 usershandlers GetPublicUserHandler handlers/users/publicuser.go:14:1
5 usershandlers isValidationError handlers/users/profile_helpers.go:40:1
5 usershandlers writeProfileError handlers/users/profile_helpers.go:13:1
5 privateuser GetPrivateProfileHandler handlers/users/privateuser/privateuser.go:12:1
5 usershandlers TestListUserMarketsReturnsErrorFromQuery handlers/users/listusers_test.go:102:1
5 usershandlers TestPersonalLinksURLValidation handlers/users/changepersonallinks_test.go:171:1
5 usershandlers TestPersonalLinksXSSPrevention handlers/users/changepersonallinks_test.go:90:1
5 usershandlers TestPasswordComplexityPatterns handlers/users/changepassword_test.go:253:1
5 usershandlers TestPasswordStrengthRequirements handlers/users/changepassword_test.go:95:1
5 usershandlers TestEmojiSpecialCharacters handlers/users/changeemoji_test.go:174:1
5 usershandlers TestDescriptionLengthValidation handlers/users/changedescription_test.go:124:1
5 metricshandlers TestGetSystemMetricsHandler_Success handlers/metrics/getsystemmetrics_test.go:26:1
5 marketshandlers (*MockResolveService).ResolveMarket handlers/markets/resolvemarket_test.go:45:1
5 marketshandlers TestMarketDetailsHandler_MarketDustZeroWithNoBets handlers/markets/marketdetailshandler_test.go:78:1
5 marketshandlers MarketDetailsHandler handlers/markets/marketdetailshandler.go:15:1
5 marketshandlers (*Handler).ProjectProbability handlers/markets/handler.go:554:1
5 marketshandlers (*Handler).GetDetails handlers/markets/handler.go:434:1
5 marketshandlers (*Handler).GetMarket handlers/markets/handler.go:135:1
5 marketshandlers TestMarketInputValidation handlers/markets/createmarket_test_security.go:234:1
5 marketshandlers TestValidateMarketResolutionTimeCustomConfig handlers/markets/createmarket_test.go:128:1
5 homepage TestService_UpdateHome_HTML handlers/cms/homepage/service_test.go:157:1
5 http TestPublicGet_ReturnsHomepageContent handlers/cms/homepage/http/handler_test.go:20:1
5 http (*Handler).AdminUpdate handlers/cms/homepage/http/handler.go:46:1
5 betshandlers TestBetAmountValidation handlers/bets/placebethandler_test_security.go:260:1
5 betshandlers TestBetInputSecurityFeatures handlers/bets/placebethandler_test_security.go:178:1
5 betshandlers TestBetInputValidation handlers/bets/placebethandler_test_security.go:8:1
5 dto TestSellBetDTOJSONRoundTrip handlers/bets/dto/dto_test.go:32:1
5 adminhandlers TestUsernameFormatValidation handlers/admin/adduser_test.go:191:1
5 adminhandlers TestUsernameValidation handlers/admin/adduser_test.go:9:1

@raisch
Copy link
Collaborator

raisch commented Dec 3, 2025

In pure functional programming, every function has only one entry and one exit (return).

Shoot for complexity of 8 or less to start.

Copy link
Collaborator

@raisch raisch left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

docs/openapi.yaml is missing five routes:

  • GET /health
  • GET /v0/setup/frontend
  • GET /v0/global/leaderboard
  • GET /v0/markets/status/{status} - see note below.
  • GET /v0/markets/{id}/projection

And still documents four legacy shortcuts:

  • GET /v0/markets/active
  • GET /v0/markets/closed
  • GET /v0/markets/resolved
  • GET /v0/marketprojection/{marketId}/{amount}/{outcome}
    • the code now uses GET /v0/markets/{id}/projection instead

Note:

Re: GET /v0/markets/status/{status} - accepts status values of "active", "closed", "resolved", "all" but the underlying code accepts "" as well. If this is intended, you should also have a GET /v0/markets/status route as well and drop the "all" status.

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