Skip to content

Commit

Permalink
doc: Note about Scala Native support (#3509)
Browse files Browse the repository at this point in the history
  • Loading branch information
xerial authored Apr 23, 2024
1 parent 0d1682f commit e1f9d00
Show file tree
Hide file tree
Showing 5 changed files with 83 additions and 34 deletions.
82 changes: 56 additions & 26 deletions docs/airframe-di.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ With Airframe DI, you can solve typical programming patterns, such as:
- Managing differently configured singletons of the same type.
- ..., etc.

Airframe DI is available for Scala 2.12, 2.13, Scala 3, and [Scala.js](https://www.scala-js.org/).
Airframe DI is available for Scala 2.12, 2.13, Scala 3, [Scala.js](https://www.scala-js.org/), and Scala Native.

In Scala, we have various approaches for dependency injection, such as [cake pattern](http://jonasboner.com/real-world-scala-dependency-injection-di/), [Google Guice](https://github.com/google/guice), [Macwire](https://github.com/adamw/macwire), [reader monad](https://softwaremill.com/reader-monad-constructor-dependency-injection-friend-or-foe/), etc. For more detailed comparison, see also [DI Framework Comparison](https://wvlet.org/airframe/docs/comparison.html), which describes pros and cons of various DI frameworks, including Airframe, Google Guice, Macwire, Dagger2, etc.

Expand Down Expand Up @@ -105,7 +105,7 @@ val d = newDesign

// Create MyApp. AppConfig instance defined in the design will be used.
// d.build[MyApp] will call new MyApp(AppConfig("Hello Airframe!")) to build a MyApp instance
d.build[MyApp]{ app: MyApp =>
d.build[MyApp]{ (app: MyApp) =>
// Do something with app
...
}
Expand Down Expand Up @@ -136,10 +136,10 @@ val design: Design =
.bind[D1].toInstance(D1(1)) // Bind D1 to a concrete instance D1(1)
.bind[D2].toInstance(D2(2)) // Bind D2 to a concrete instance D2(2)
.bind[D3].toInstance(D3(3)) // Bind D3 to a concrete instance D3(3)
.bind[P].toProvider{ d1:D1 => P(d1) } // Create a singleton P by resolving D1 from the design
.bind[P].toProvider{ (d1:D1) => P(d1) } // Create a singleton P by resolving D1 from the design
.bind[P].toProvider{ (d1:D1, d2:D2) => P(d1, d2) } // Resolve D1 and D2
.bind[P].toProvider{ provider _ } // Use the given function as a provider
.bind[P].toEagerSingletonProvider{ d1:D1 => P(d1) } // Create an eager singleton using the provider function
.bind[P].toEagerSingletonProvider{ (d1:D1) => P(d1) } // Create an eager singleton using the provider function
```

If you define multiple bindings to the same type (e.g., P), the last binding will have the highest precedence.
Expand Down Expand Up @@ -206,14 +206,14 @@ import wvlet.airframe._
object MyServerService {
val design = newDesign
.bind[Server]
.onInit{ x:Server => ... } // Called when the object is initialized
.onInject{ x:Server => ... } // Called when the object is injected
.onStart{ x:Server => ... } // Called when session.start is called
.afterStart{ x:Server => ... } // Called after onStart lifecycle is finished.
// Use this only when you need to add an extra startup process for testing.
.beforeShutdown{ x:Server => ...} // Called right before all shutdown hook is called
// Useful for adding pre-shutdown step
.onShutdown{ x:Server => ... } // Called when session.shutdown is called
.onInit{ (x:Server) => ... } // Called when the object is initialized
.onInject{ (x:Server) => ... } // Called when the object is injected
.onStart{ (x:Server) => ... } // Called when session.start is called
.afterStart{ (x:Server) => ... } // Called after onStart lifecycle is finished.
// Use this only when you need to add an extra startup process for testing.
.beforeShutdown{ (x:Server) => ...} // Called right before all shutdown hook is called
// Useful for adding pre-shutdown step
.onShutdown{ (x:Server) => ... } // Called when session.shutdown is called
)
}
```
Expand Down Expand Up @@ -268,7 +268,7 @@ trait MyService {
}
```

These annotations are not supported in Scala.js, because Scala.js has no run-time reflection to read annotations in a class. For maximum compatibility, we recommend using onStart/onShutdown hooks or implementing AutoCloseable interface.
These annotations are not supported in Scala.js and Scala Native, because other than JVM, there is no run-time reflection capability to read annotations in a class. For maximum compatibility, we recommend using onStart/onShutdown hooks or implementing AutoCloseable interface.


## Session
Expand All @@ -277,15 +277,15 @@ These annotations are not supported in Scala.js, because Scala.js has no run-tim

```scala
val session = design.newSession
val a = session.build[A] { obj: A =>
val a = session.build[A] { (obj: A) =>
// Do something with obj
}
```

If you need a typed-return value, you can use `design.run[A, B](f: A=>B)`:

```scala
val ret: Int = design.run { a: A =>
val ret: Int = design.run { (a: A) =>
// Do something with a and return a value
1
}
Expand All @@ -311,7 +311,7 @@ finally {
To simplify this session management, you can use `Design.build[A]` to start and shutdown a session automatically:

```scala
design.build[P]{ p:P => // session.start will be called, and a new instance of P will be created
design.build[P]{ (p:P) => // session.start will be called, and a new instance of P will be created
// do something with P
}
// session.shutdown will be called here
Expand Down Expand Up @@ -417,26 +417,28 @@ bind[Map[_,_]]

Behind the scene, Airframe uses [Surface](https://github.com/wvlet/airframe/surface/) as identifier of types so that we can extract these types identifiers at compile time.

### Type Alias Binding
### Tagged Type Binding

If you need to bind different objects to the same data type, use tagged type:

If you need to bind different objects to the same data type, use type aliases of Scala. For example,
```scala
import wvlet.airframe.surface.tag.*
case class Fruit(name: String)

type Apple = Fruit
type Banana = Fruit
trait Apple
trait Banana

class TaggedBinding(apple:Apple, banana:Banana)
class TaggedBinding(apple:Fruit @@ Apple, banana: Fruit @@ Banana)

```

Alias binding is useful to inject primitive type values:
Tagged-type binding is useful to inject primitive type values:
```scala
import wvlet.airframe._

type Env = String
trait Env

class MyService(env:Env, session: Session) {
class MyService(env:String @@ Env, session: Session) {
// Conditional binding
lazy val threadManager = env match {
case "test" => new TestingThreadManager(...) // prepare a testing thread manager
Expand All @@ -449,11 +451,11 @@ val coreDesign = newDesign

val testingDesign =
coreDesign.
bind[Env].toInstance("test")
bind[String @@ Env].toInstance("test")

val productionDesign =
coreDesign
.bind[Env].toInstance("production")
.bind[String @@ Env].toInstance("production")
```

### Multi-Binding
Expand Down Expand Up @@ -524,6 +526,34 @@ Another workaround is setting `fork in run := true` or `fork in test := test` to
Test / classLoaderLayeringStrategy := ClassLoaderLayeringStrategy.Flat
```

## Type alias cannot be used in provider binding

Since Scala 3, type aliases can be eagerly resolved at compile time [#2200](https://github.com/wvlet/airframe/issues/2200). So type alias might not work for binding instances, especitally type aliases are used for provider bindings:

```scala

type MyString = String

Design.newDesign
.bind[MyString].toInstance("hello")
.bind[X].toProvider{ (s: MyString) => println(s) }

// MISSING_DEPENDENCY: String error will be thrown when building X
// because { (s: MyString) => ... } is eagerly resolved to { (s: String) => ... } at compile-time
```

A workaround is using tagged types:

```scala
import wvlet.airframe.surface.tag.*
trait Env

Design.newDesign
.bind[String @@ Env].toInstance("hello")
.bind[X].toProvider{ (s: String @@ Env) => println(s) }
```


## Debugging DI

To check the runtime behavior of Airframe's dependency injection, set the log level of `wvlet.airframe.di` to `debug` or `trace`:
Expand Down
2 changes: 1 addition & 1 deletion docs/airframe-log.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ libraryDependencies += "org.wvlet.airframe" %%% "airframe-log" % "(version)"
- Easy to customize your own log format and log levels *inside* the code. No external XML configuration is required.
- **Production ready**
- airframe-log has built-in handlers for log file rotations, asynchronous logging.
- Scala 2.12, 2.13, Scala.js support
- Scala 2.12, 2.13, 3.x, Scala.js, Scala Native support


### Using LogSupport trait
Expand Down
2 changes: 1 addition & 1 deletion docs/airframe-rx.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ __build.sbt__
libraryDependencies += "org.wvlet.airframe" %% "airframe-rx" % "(version)"
```

For Scala.js, use `%%%`:
For Scala.js and Scala Native, use `%%%`:
```scala
libraryDependencies += "org.wvlet.airframe" %%% "airframe-rx" % "(version)"
```
Expand Down
2 changes: 1 addition & 1 deletion docs/airspec.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ AirSpec uses `test("...") { ... }` syntax for writing test cases. This style req
- The lifecycle (e.g., start and shutdown) of the injected services will be properly managed.
- Handy keyword search for _sbt_: `> testOnly -- (a pattern for class or method names)`
- Property-based testing integrated with [ScalaCheck](https://www.scalacheck.org/)
- Scala 2.12, 2.13, 3, Scala.js, Scala Native support
- Scala 2.12, 2.13, Scala 3, Scala.js, Scala Native support

To start using AirSpec, read [Quick Start](#quick-start).

Expand Down
29 changes: 24 additions & 5 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ title: Overview

<img style = "float: right; padding: 10px;" width="150px" src="../img/logos/airframe_icon_small.png"/>

Airframe is a collection of essential building blocks for writing full-fledged applications in Scala and Scala.js.
Airframe is a collection of essential building blocks for writing full-fledged applications in Scala, Scala.js, and Scala Ntive.

- [Release Notes](release-notes.md)
- [Source Code (GitHub)](https://github.com/wvlet/airframe)
Expand Down Expand Up @@ -73,12 +73,27 @@ libraryDependencies ++= Seq(
"org.wvlet.airframe" %%% "airframe-surface" % AIRFRAME_VERSION, // Object surface inspector
"org.wvlet.airframe" %%% "airframe-ulid" % AIRFRAME_VERSION, // ULID generator
)

// For Scala Native 0.5.x, the following libraries can be used (Since Airframe 24.4.2):
// Note: Only Scala 3 is supported for Scala Native
libraryDependencies ++= Seq(
"org.wvlet.airframe" %%% "airframe" % AIRFRAME_VERSION, // Dependency injection
"org.wvlet.airframe" %%% "airframe-codec" % AIRFRAME_VERSION, // MessagePack-based schema-on-read codec
"org.wvlet.airframe" %%% "airframe-control" % AIRFRAME_VERSION, // Library for retryable execution
"org.wvlet.airframe" %%% "airframe-json" % AIRFRAME_VERSION, // Pure Scala JSON parser
"org.wvlet.airframe" %%% "airframe-log" % AIRFRAME_VERSION, // Logging
"org.wvlet.airframe" %%% "airframe-msgpack" % AIRFRAME_VERSION, // Pure-Scala MessagePack
"org.wvlet.airframe" %%% "airframe-metrics" % AIRFRAME_VERSION, // Metrics units
"org.wvlet.airframe" %%% "airframe-rx" % AIRFRAME_VERSION, // ReactiveX interface
"org.wvlet.airframe" %%% "airframe-surface" % AIRFRAME_VERSION, // Object surface inspector
"org.wvlet.airframe" %%% "airframe-ulid" % AIRFRAME_VERSION, // ULID generator
)
```

If you need an early access to the latest features, [snapshot versions](https://oss.sonatype.org/content/repositories/snapshots/org/wvlet/airframe/) are also available for each main branch commit. To use snapshot versions, add Sonatype snapshot repository to your resolver setting:
For an early access to the latest features, [snapshot versions](https://oss.sonatype.org/content/repositories/snapshots/org/wvlet/airframe/) are also available for each main branch commit. To use snapshot versions, add Sonatype snapshot repository to your resolver setting:

```scala
resolvers += Resolver.sonatypeRepo("snapshots")
resolvers += Resolver.sonatypeOssRepos("snapshots")
```

## Usage Guides
Expand Down Expand Up @@ -206,9 +221,13 @@ Other than `@Endpoint` annotations, this is the same with regular Scala class de
A client code to access this API is also simple like this:

```scala
import wvlet.airframe.http.Http

// Accessing the server using an http client
client.get[ServerInfo]("/v1/info") // ServerInfo("1.0")
client.post("/v1/user", User(1, "Ann")) // User(1, "Ann")
val client = Http.client.newSyncClient("http://localhost:8080")

client.readAs[ServerInfo](Http.GET("/v1/info")) // ServerInfo("1.0")
client.call[User, User](Http.POST("/v1/user"), User(1, "Ann")) // User(1, "Ann")
```

Mapping from HTTP responses to case classes is handled by [airframe-codec](airframe-codec.md).
Expand Down

0 comments on commit e1f9d00

Please sign in to comment.