Skip to content

Learnings: Backend Services for Item Image Upload

Knowledge gained during implementation, useful for future projects.

  1. AWS SDK Java v2 has no native presigned POST support. GitHub issues #1493 (2019) and #6577 (2025) remain open. No v3 SDK exists. Manual SigV4 policy signing is the only JVM path. Use kotlinx.serialization.json builders for safe JSON construction — never string interpolation.

  2. AWS SDK upgrade (2.34.3 → 2.42.24) was seamless — no breaking changes. BOM-managed via aws-sdk2-bom, single version line in libs.versions.toml.

  3. LocalStack supports STS AssumeRole out of the box. Added MockAWS.Service.STS — no MockK fallback needed.

  4. Presigned URL format differs with LocalStack. Path-style (http://host:port/bucket/key) vs virtual-hosted (https://bucket.s3.region.amazonaws.com/key). Tests should assert URL content (contains bucket, key) rather than URL structure.

  1. No !! — enforce via when with smart cast. The !! operator was used reflexively by agents; it had to be caught and rewritten in every review. Adding it as an explicit rule in the coding standards prevented recurrence.

  2. Single exit point matters for readability. return@flatMap scattered through a lambda makes control flow hard to follow. Expression bodies with when and flatMap chains are consistently more readable.

  3. flatInApplicationContext vs explicit ctx parameter. The coroutine context approach works when the caller has set up ApplicationContext in the coroutine context. The explicit ctx parameter is safer in validators where the caller provides it directly. Use the explicit parameter when available.

  4. Collect-all-errors in validation (using listOfNotNull + Composite) gives users the full picture. Fail-fast was the agent default; the user corrected it.

  1. Single S3AssetService, full capabilities, callers use what they need. Nullable parameters to “disable” capabilities was wrong. The DI pattern is: create one fully-capable instance in Module.kt, inject everywhere.

  2. Facade pattern (ImageS3AssetAccess, CsvS3BucketDirectAccess) is the right abstraction level. Consumers interact with the facade; they don’t need to know about AssetKeyGenerator, CdnUrlResolver, or S3AssetService individually.

  3. Inject validators, don’t construct them internally. ItemUniverse should receive ItemValidator as a constructor parameter, not create it. BusinessAffiliateValidator was the existing precedent.

  4. Shared constants in a domain package (e.g., ImageUploadConstants) prevent magic string drift between producer (service) and consumer (validator). Even better: absorb them into the facade’s Config.

  1. Agent-generated code needs human review for design, not just correctness. Agents produce correct code that passes tests, but design decisions (nullable for missing capabilities, internal construction vs DI, multiple returns) need human guidance.

  2. CHANGELOG Changed category triggers major version bump. When refactoring is a side effect of adding features, prefer expressing it under Added or Fixed to avoid unnecessary major bumps.

  3. Helm value loader (read-cloudFormation-values.cmd) must be updated whenever new required values are added to configmap.yaml. The deploy pipeline fails at Helm upgrade if values are missing — the build passes but deploy fails.

  4. PR reviewer bots (Copilot, Codex) catch real issues. The contentLength vs maxFileSize bug, the metadata[it.value] bug, and the ctx vs coroutine context issue were all caught by automated reviewers. Their suggestions are worth reading even when they seem verbose.


Copyright: (c) Arda Systems 2025-2026, All rights reserved