Skip to content

Commit 3721a09

Browse files
authored
Implement dev mode basics (#3644) (#3709)
1 parent edba470 commit 3721a09

25 files changed

+233
-21
lines changed

build.sbt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -423,7 +423,7 @@ lazy val zioHttpTestkit = (project in file("zio-http-testkit"))
423423
testFrameworks += new TestFramework("zio.test.sbt.ZTestFramework"),
424424
libraryDependencies ++= netty ++ Seq(
425425
`zio`,
426-
`zio-test`,
426+
"dev.zio" %% "zio-test" % ZioVersion,
427427
`zio-test-sbt`,
428428
),
429429
)

docs/concepts/dev-mode.md

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
# Dev / Preprod / Prod Modes
2+
3+
ZIO HTTP provides a simple built-in notion of application "mode" so you can adapt behavior (e.g. enable extra diagnostics in development, stricter settings in production, other routes, different error handling) without wiring your own config keys everywhere.
4+
5+
The available modes are:
6+
7+
- `Mode.Dev` (default if nothing is configured)
8+
- `Mode.Preprod` (a staging / pre‑production environment)
9+
- `Mode.Prod` (production)
10+
11+
## Reading the Current Mode
12+
13+
Use any of the following helpers:
14+
15+
```scala
16+
import zio.http.Mode
17+
18+
// Full value
19+
def m: Mode = Mode.current
20+
21+
// Convenience booleans
22+
val isDev = Mode.isDev
23+
val isPreprod = Mode.isPreprod
24+
val isProd = Mode.isProd
25+
```
26+
27+
## Configuring the Mode
28+
29+
The mode is determined in this precedence order:
30+
31+
1. JVM System Property: `-Dzio.http.mode=<dev|preprod|prod>`
32+
2. Environment Variable: `ZIO_HTTP_MODE=<dev|preprod|prod>`
33+
3. Fallback: `dev`
34+
35+
Examples:
36+
37+
```bash
38+
# Using a JVM system property
39+
sbt "run -Dzio.http.mode=preprod"
40+
41+
# Using an environment variable (takes effect if the system property is NOT set)
42+
ZIO_HTTP_MODE=prod sbt run
43+
```
44+
45+
Unknown values cause a warning on stderr and the mode falls back to `dev`:
46+
47+
## Typical Use Cases
48+
49+
You can branch on the mode to enable / disable features:
50+
51+
```scala
52+
import zio._
53+
import zio.http._
54+
55+
val extraRoutes: Routes[Any, Nothing] =
56+
if (Mode.isDev) SwaggerUI.routes("docs", OpenAPIGen.empty)
57+
else Routes.empty
58+
59+
val baseRoutes: Routes[Any, Nothing] = Routes(
60+
Method.GET / "health" -> handler(Response.ok)
61+
)
62+
63+
val appRoutes = baseRoutes ++ extraRoutes
64+
```
65+
66+
Or adapt server config:
67+
68+
```scala
69+
val serverConfig =
70+
if (Mode.isProd) Server.Config.default
71+
.leakDetection(false)
72+
.requestDecompression(true)
73+
else Server.Config.default
74+
.leakDetection(true) // extra visibility in dev
75+
.maxThreads(4) // keep lighter in local dev
76+
```
77+
78+
## Testing Modes
79+
80+
Inside tests you generally want to *temporarily* switch the mode to verify conditional behavior. The testkit provides aspects in `zio.http.HttpTestAspect`:
81+
82+
- `HttpTestAspect.devMode`
83+
- `HttpTestAspect.preprodMode`
84+
- `HttpTestAspect.prodMode`
85+
86+
Each aspect sets the mode for the duration of the test, restoring the previous mode afterward. This allows you to write tests that depend on specific modes without affecting other tests.
87+
88+
Example:
89+
90+
```scala
91+
import zio.test._
92+
import zio.http._
93+
94+
object ModeExamplesSpec extends ZIOSpecDefault {
95+
def spec = suite("ModeExamplesSpec")(
96+
test("enables preprod logic") {
97+
assertTrue(Mode.current == Mode.Preprod)
98+
} @@ HttpTestAspect.preprodMode,
99+
100+
test("enables prod logic") {
101+
assertTrue(Mode.isProd)
102+
} @@ HttpTestAspect.prodMode,
103+
) @@ TestAspect.sequential // IMPORTANT, see below
104+
}
105+
```
106+
107+
### Why `TestAspect.sequential`?
108+
109+
The mode is stored per JVM. When you apply different mode aspects to multiple tests in the **same suite**, running them in parallel could cause races (e.g. one test reads prod while another just switched to preprod). Adding `@@ TestAspect.sequential` ensures the suite’s tests execute one after another so each mode override is isolated.
110+
111+
If every test suite uses only one mode (or you wrap all tests in a single aspect at the suite level), sequential execution is not strictly necessary. It is required only when multiple tests in the same suite each apply different mode aspects.
112+
113+
## Quick Reference
114+
115+
| Task | How |
116+
|------|-----|
117+
| Read current mode | `Mode.current` |
118+
| Check if dev | `Mode.isDev` |
119+
| Run in preprod | `-Dzio.http.mode=preprod` or `ZIO_HTTP_MODE=preprod` |
120+
| Override in a test | `test("...") { ... } @@ HttpTestAspect.prodMode` |
121+
| Avoid race conditions | Apply `@@ TestAspect.sequential` to suite when multiple mode aspects are used |
122+
123+
## When *Not* to Use Mode
124+
125+
For complex environment-dependent configuration (database URLs, secrets, feature flags) prefer a dedicated configuration service (e.g. `zio-config`).

website/sidebars.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,7 @@ const sidebars = {
173173
type: "category",
174174
collapsed: false,
175175
label: "Concepts",
176-
items: ["concepts/routing", "concepts/middleware", "concepts/endpoint"],
176+
items: ["concepts/routing", "concepts/middleware", "concepts/endpoint", "concepts/dev-mode"],
177177
},
178178
],
179179

zio-http-benchmarks/src/main/scala-2.13/zio/http/benchmarks/EndpointBenchmark.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ import sttp.tapir.{Endpoint => TEndpoint, endpoint => tendpoint, path => tpath,
7070
// [info] EndpointBenchmark.benchmarkSmallDataZioCollect thrpt 2 701.566 ops/s
7171

7272
@State(Scope.Thread)
73-
@BenchmarkMode(Array(Mode.Throughput))
73+
@BenchmarkMode(Array(org.openjdk.jmh.annotations.Mode.Throughput))
7474
@OutputTimeUnit(TimeUnit.SECONDS)
7575
class EndpointBenchmark {
7676
// implicit val actorSystem: ActorSystem = ActorSystem("api-benchmark-actor-system")

zio-http-benchmarks/src/main/scala/benchmark/FormToQueryBenchmark.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import zio.http._
99
import org.openjdk.jmh.annotations._
1010

1111
@State(org.openjdk.jmh.annotations.Scope.Thread)
12-
@BenchmarkMode(Array(Mode.Throughput))
12+
@BenchmarkMode(Array(org.openjdk.jmh.annotations.Mode.Throughput))
1313
@OutputTimeUnit(TimeUnit.SECONDS)
1414
class FormToQueryBenchmark {
1515

zio-http-benchmarks/src/main/scala/benchmark/MethodLookupBenchmark.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import zio.http.endpoint.Endpoint
1010
import org.openjdk.jmh.annotations._
1111

1212
@State(Scope.Thread)
13-
@BenchmarkMode(Array(Mode.Throughput))
13+
@BenchmarkMode(Array(org.openjdk.jmh.annotations.Mode.Throughput))
1414
@OutputTimeUnit(TimeUnit.SECONDS)
1515
class MethodLookupBenchmark {
1616

zio-http-benchmarks/src/main/scala/benchmark/RoundtripBenchmark.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import zio.http._
1111
import org.openjdk.jmh.annotations._
1212

1313
@State(org.openjdk.jmh.annotations.Scope.Thread)
14-
@BenchmarkMode(Array(Mode.Throughput))
14+
@BenchmarkMode(Array(org.openjdk.jmh.annotations.Mode.Throughput))
1515
@OutputTimeUnit(TimeUnit.SECONDS)
1616
class RoundtripBenchmark {
1717

zio-http-benchmarks/src/main/scala/benchmark/RoutesBenchmark.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import zio.http.{Handler, Method, Request, Routes}
1010
import org.openjdk.jmh.annotations._
1111

1212
@State(Scope.Thread)
13-
@BenchmarkMode(Array(Mode.Throughput))
13+
@BenchmarkMode(Array(org.openjdk.jmh.annotations.Mode.Throughput))
1414
@OutputTimeUnit(TimeUnit.SECONDS)
1515
class RoutesBenchmark {
1616

zio-http-benchmarks/src/main/scala/zhttp.benchmarks/CachedDateHeaderBenchmark.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import zio.http.internal.DateEncoding
88
import org.openjdk.jmh.annotations._
99

1010
@State(Scope.Benchmark)
11-
@BenchmarkMode(Array(Mode.AverageTime))
11+
@BenchmarkMode(Array(org.openjdk.jmh.annotations.Mode.AverageTime))
1212
@OutputTimeUnit(TimeUnit.NANOSECONDS)
1313
@Threads(16)
1414
@Fork(1)

zio-http-benchmarks/src/main/scala/zhttp.benchmarks/ClientBenchmark.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import org.openjdk.jmh.annotations._
1212

1313
@nowarn
1414
@State(org.openjdk.jmh.annotations.Scope.Benchmark)
15-
@BenchmarkMode(Array(Mode.Throughput))
15+
@BenchmarkMode(Array(org.openjdk.jmh.annotations.Mode.Throughput))
1616
@OutputTimeUnit(TimeUnit.SECONDS)
1717
@Warmup(iterations = 3, time = 3)
1818
@Measurement(iterations = 3, time = 3)

0 commit comments

Comments
 (0)