Stack conventions
Scala
Scala 2.13 and Scala 3 services and libraries built with sbt, functional style with Cats Effect or ZIO where the repository uses them.
Scala 2.13 and Scala 3 services and libraries built with sbt, functional style with Cats Effect or ZIO where the repository uses them.
Used for: scala.
Principles
- Default to immutability:
val, immutable collections and case classes; confinevarand mutable collections to small local scopes. - Model the domain with algebraic data types: case classes for products, sealed traits or Scala 3
enumfor sums, matched exhaustively. - Make illegal states unrepresentable: opaque types (Scala 3) or value classes for domain primitives, smart constructors that validate.
- Represent absence with
Optionand expected failure withEither/ValidatedNelor the effect's error channel — nevernull, and avoid exceptions for control flow. - Follow the repo's effect system (Cats Effect
IO, ZIO, orFuture) consistently; do not mix effect types within a module without a clear boundary. - Keep side effects at the edges: pure functions in the core, effects composed and run once at the application's entry point.
- Prefer total functions: no
.getonOption,.headon possibly empty collections, or partial pattern matches. - Use type classes and
given/using(or implicits in Scala 2) deliberately, with instances in companion objects; avoid implicit conversions. - Write for readability over cleverness: name intermediate values, limit operator symbols and deeply nested combinators.
Conventions
Accessibility
Concurrency
- Use the effect system's fibers and structured concurrency (
parTraverse,ZIO.foreachPar) instead of raw threads; bound parallelism for large fan-outs. - Run blocking calls on the blocking pool (
IO.blocking,ZIO.attemptBlocking); never block the compute pool. - With
Future, pass an explicitExecutionContextand neverAwait.resultin production code.
Dependencies
- Keep dependency versions in one place in the build (
build.sbt/project/Dependencies.scala) and use%%for Scala cross-versioned artifacts. - Enable strict compiler options (
-deprecation,-feature, unused warnings; Scala 3-Wunused:all, or sbt-tpolecat) where the repo allows.
Documentation
- Scaladoc on public APIs; document type class requirements and effect semantics.
- Comment why, not what; keep the README's build, run and configuration steps current when they change.
Errors
- Use typed errors (ADTs) in
Eitheror the effect's error channel for domain failures; reserve exceptions for defects. - Never use
Tryorcatchto hide errors; handle or propagate with context.
Logging
- Use the effect-aware logger the repo uses (log4cats, ZIO logging) or SLF4J; structured context, no secrets or personal data.
- Log at boundaries with structured context (request id, entity id) and let errors carry their cause instead of logging and rethrowing.
Naming
- Classes, traits, objects and type parameters UpperCamelCase; methods, values and variables lowerCamelCase; constants UpperCamelCase per the style guide.
- Avoid symbolic method names unless they are well-known operators in the domain.
Performance
- Choose collections deliberately (
Vectorfor indexed access,Listfor prepend/recursion,ArraySeq/arrays in hot loops); useviewor iterators for long transformation chains. - Use tail recursion with
@tailrecor folds instead of unbounded recursion.
Security
- Use parameterised queries (Doobie
sqlinterpolator, Slick, Skunk); never build SQL with string concatenation. - Decode untrusted input into validated domain types at the boundary.
- Keep secrets in environment variables or a secret store, loaded through the config library (Ciris, PureConfig), never in
application.confcommitted to the repo. - Never pass external input to
sys.processwith a shell, to reflection or to deserialisers of arbitrary classes (injection). - Scan dependencies for known vulnerabilities (sbt-dependency-check) and pin plugin and library versions in the build.
Structure
- One top-level public type per file named after it (companions together); packages follow directories.
- Keep pure domain code separate from infrastructure (HTTP, database, messaging) modules.
Testing
- Use the repo's framework (MUnit, ScalaTest, specs2, ZIO Test, weaver); property-based tests with ScalaCheck for pure logic.
- Test effectful code in the effect (munit-cats-effect, ZIO Test) and control time with test clocks rather than sleeping.
- Unit-test pure functions and services with fakes; integration tests (Testcontainers-scala, http4s client against the routes) cover database and HTTP boundaries.
- Name tests after the behaviour (
test("rejects an order without lines")), one behaviour per test. - Done means tested: new or changed behaviour ships with tests in the same change, and a fixed bug gets a regression test that fails without the fix.
Review checklist
- Is data immutable (vals, case classes, immutable collections)?
- Are sum types sealed/enums and matched exhaustively?
- Is null absent, with Option used for optional values?
- Are there no partial calls such as Option.get or head on possibly empty collections?
- Are expected failures typed rather than thrown?
- Is the repo's effect system used consistently, with blocking work on the blocking pool?
- Are resources acquired with Resource, Scope or Using so they are always released?
- Are database queries parameterised?
- Are implicits/givens limited and free of implicit conversions?
- Do tests cover the logic, using property-based tests where they add value?
- Is the code readable without deep combinator nesting or custom operators?
Quality checks (run when your repository has the tool configured)
| Check | Kind | Tool | Blocks the pull request |
|---|---|---|---|
scalafmt |
format | scalafmt | no |
scalafix |
lint | scalafix | no |
sbt-dependency-check |
security | sbt-dependency-check | no |
gitleaks |
security | gitleaks | no |