Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions _pages/zio-streams/zio-kafka-part1.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ bit to get it working.
I used [Easy Ways to Generate Test Data in Kafka](https://www.confluent.io/blog/easy-ways-generate-test-data-kafka)

First we create topic2 using datagen. I used the sample datagen-users.json file.
```
```json
{
"name": "datagen-users",
"config": {
Expand All @@ -44,6 +44,6 @@ First we create topic2 using datagen. I used the sample datagen-users.json file.
which I put in the streams/assets folder of the source for the project.


```
```sh
confluent local config datagen-users -- -d ./datagen-users.json
```
```
25 changes: 13 additions & 12 deletions _pages/zio-uzhttp-sttp/uzhttp-sttp.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ A few months ago I wrote a blog on [using http4s from zio](../zio-http4s/intro.m

In that blog I went into quite a lot of detail looking at authentication and at encoding, because both had proved time-consuming to get right.

Recently, a new zio-based been launched by the authors of the [polynote](https://polynote.org/) - the netflix-originated analytics notebook. (uzhttp)[(https://github.com/polynote/uzhttp)] is a micro-http server - it's very lightweight and comes with no bells and whistles, though it does support websockets. It's used in polynote apparently. But it does come with a health warning - and may be too micro for most people.
Recently, a new zio-based been launched by the authors of the [polynote](https://polynote.org/) - the netflix-originated analytics notebook. [uzhttp](https://github.com/polynote/uzhttp) is a micro-http server - it's very lightweight and comes with no bells and whistles, though it does support websockets. It's used in polynote apparently. But it does come with a health warning - and may be too micro for most people.

Nevertheless, I thought I'd compare it with the http4s solution to see how easy it was to work with. And this blog shows the first fruits.

Expand All @@ -22,7 +22,7 @@ All source code is on my [github](https://github.com/TimPigden/zio-http4s-exampl
Thanks to the zio regulars who gave me tips in this work plus Jeremy Smith from polynote.org. Please comment or drop me a line if there are any errors or omissions or opportunities to improve the blog or the code.

## Client
Http4s provides both a server and a client. And we need both a server and a client for our testing. So rather than re-use http4s I thought I'd try (sttp)[https://github.com/softwaremill/sttp]. Unlike uzhttp, sttp is a battled hardend scala http client solution that's been around quite a while. It has versions for a variety of backend and is pretty comprehensive. Importantly, one of those backends is a Netty-based zio implementation and this is what we shall be using.
Http4s provides both a server and a client. And we need both a server and a client for our testing. So rather than re-use http4s I thought I'd try [sttp](https://github.com/softwaremill/sttp). Unlike uzhttp, sttp is a battled hardend scala http client solution that's been around quite a while. It has versions for a variety of backend and is pretty comprehensive. Importantly, one of those backends is a Netty-based zio implementation and this is what we shall be using.

I'm not going to go into detail of sttp - it's very well documented and was really easy to use, so just go and look at the website

Expand All @@ -41,7 +41,7 @@ object Hello1Routes {
}
```

So that's pretty simple - it's a PartialFunction taking a uzhttp.Request and returning an IO[HTTPError, Response]. It doesn't do a lot!.
So that's pretty simple - it's a PartialFunction taking a uzhttp.Request and returning an `IO[HTTPError, Response]`. It doesn't do a lot!.

Here's the test
```scala
Expand Down Expand Up @@ -79,7 +79,7 @@ Two tests, each of which checks the server is running and then uses SttpClient t

Because the test is running across the wire, you won't see any uzhttp code or types here. We just need to know that the server is started and is running.

The actual work is done in the ZIO layers that have been created - see Zio documentation and [my previous blog]()../zlayer/Examples.md) for more about Zio layers. This article assumes you have familiarity with the concept.
The actual work is done in the ZIO layers that have been created - see Zio documentation and [my previous blog](../zlayer/Examples.md) for more about Zio layers. This article assumes you have familiarity with the concept.

So we're creating 2 layers - the first is the client. This puts SttpClient.send .. into our context. It's created with a direct call to ```AsyncHttpClientZioBackend.layer()```
and that's it. Like I said - really simple.
Expand All @@ -93,7 +93,7 @@ The server is a bit more work. We call the function serverLayer with the partial
)
```

The uzhttp4s Server.builder has a builder pattern to create and start the builder. It returns a ZIO Managed which we can simply wrap up in the ZLayer.fromManaged to give the layer.
The uzhttp4s Server.builder has a builder pattern to create and start the builder. It returns a ZIO Managed which we can simply wrap up in the `ZLayer.fromManaged` to give the layer.

To check server is started and running
```scala
Expand All @@ -110,7 +110,8 @@ case class Person(name: String, age: Int)
object Person {
val donald = Person("Donald Trump", 73)
val joe = Person("Joe Biden", 76)
}```
}
```

We obviously need to get and post data, so our XmlRoutes is a bit more involved:
```scala
Expand Down Expand Up @@ -158,7 +159,7 @@ object Encoders {
}

```
So we've defined an typeclass XmlWriter[A]. In the real world I will be using a magnolia-based XmlWriter but here we have a a scala.xml dsl-based one.
So we've defined an typeclass `XmlWriter[A]`. In the real world I will be using a magnolia-based XmlWriter but here we have a a scala.xml dsl-based one.

The code is pretty obvious, we pretty-print the xml (for testing) and then use the uzhttp Response.const function to actually create a response. There are other functions to create Responses in uzhttp. They are not documented - you will have to go and look at the source code - but take comfort - it's nice and easy to read and quite short. This code is a direct rip-off the Response.html method but with different content type

Expand Down Expand Up @@ -295,7 +296,7 @@ object Authorizer {
Essentially the Authorizer takes an access token which has been provided by the external environment. It will then validate this to return AuthInfo - in this case just a wrapped string but in reality will be something more complex (and yes in real world probably tokens will expire and so on)

Next we provide a dummy Authorizer
```
```scala
val friendlyAuthorizer: Service = { token =>
token match {
case "friend" => IO.succeed(AuthInfo("Vetted"))
Expand Down Expand Up @@ -457,13 +458,13 @@ It's slightly more complicated due to the fact that we have a ZIO of a Managed t
Personally, I find having long lists of PartialFunction case matches not particularly satisfactory. uzhttp authors say they had no intention of making a DSL but can we make our code more fluent.

One possibility was suggested to me on the zio-users discord channe. Instead of combining partial functions, we can make it more "zio-like" with the following:
```
```scala
type HRequest = Has[Request]

type EndPoint[R <: HRequest] = ZIO[R, Option[HTTPError], Response]
```

Much like our partial function, it takes a request and returns a response. But what's the Option[HTTPError] about? Essentially, what it does is allows us to give us 3 return possibilities:
Much like our partial function, it takes a request and returns a response. But what's the `Option[HTTPError]` about? Essentially, what it does is allows us to give us 3 return possibilities:
- the Response, if our EndPoint matches the request
- An error of Some(error) if there's something wrong with the Request
- An "error" of None - if the EndPoint doesn't match the response
Expand Down Expand Up @@ -497,11 +498,11 @@ where the helper methods are things like:
```

So that's individual EndPoints. How do we chain them together - what is the equivalent to "orElse". So looking through the zio RC18-2 I couldn't see a really slick way of doing this. Asking on the Discord channel, essentially elicited the response, there's nothing there yet, ok, we've just done something. So in zio RC19 or 1.0.0 or a current (post about April 20th) SNAPSHOT you will be able to do this:
```
```scala
val routes = president orElseOptional contender orElseOptional whatIsMyName
```
But for the impatient, I've got the following:
```
```scala
def combineRoutes[R <: HRequest](h: EndPoint[R], t: EndPoint[R]*): EndPoint[R] =
t.foldLeft(h)((acc, it) =>
acc catchSome { case None => it }
Expand Down
12 changes: 6 additions & 6 deletions _pages/zlayer/Examples.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ The point about Teams is to test dependencies between modules that we've created
```
Teams will pick a team from the available names, making _size_ selections.

Following Module usage patterns, although **pickTeam** needs a Names to function, we don't put it in as a ZIO[Names, Nothing, Set[String]] - instead we hold a reference in the **TeamsImpl**
Following Module usage patterns, although **pickTeam** needs a Names to function, we don't put it in as a `ZIO[Names, Nothing, Set[String]]` - instead we hold a reference in the **TeamsImpl**

Our first test is straight-forward
```scala
Expand Down Expand Up @@ -147,7 +147,7 @@ created namesImpl
[36mRan 1 test in 225 ms: 1 succeeded, 0 ignored, 0 failed[0m
```
Looking back to the definition of NamesImpl
```
```scala
case class NamesImpl(random: Random.Service) extends Names.Service {
println(s"created namesImpl")
def randomName =
Expand Down Expand Up @@ -226,7 +226,7 @@ And that's it.

## Throwable Errors

The above code all assumes you're returning ZLayer[R, Nothing, T] - in other words the construction of the environment service has Nothing type. But if it's doing something like reading from a file or a database, then very likely it will be ZLayer[R, Throwable, T] - because that sort of thing often involves precisely the sort of external effect that will throw an exception. So imagine Names construction had a throwable error. For your tests, the way to get round it is like this:
The above code all assumes you're returning `ZLayer[R, Nothing, T]` - in other words the construction of the environment service has Nothing type. But if it's doing something like reading from a file or a database, then very likely it will be `ZLayer[R, Throwable, T]` - because that sort of thing often involves precisely the sort of external effect that will throw an exception. So imagine Names construction had a throwable error. For your tests, the way to get round it is like this:
```scala
val live: ZLayer[Random, Throwable, Names] = ???
```
Expand All @@ -239,7 +239,7 @@ The mapError turns the throwable into a test failure - which is what you want -
## More ZEnv Cases

The "standard" environment items include clock and random. In out Names, we used Random. But what if we also want one of these items further "down" our dependencies. For this purpose I've created a second version of History - History2 - and this needs Clock to create an instance.
```
```scala
object History2 {

trait Service {
Expand All @@ -260,13 +260,13 @@ The "standard" environment items include clock and random. In out Names, we used
}
```
It's not a very useful example - but the important part is that the line
```
```scala
someTime <- ZIO.accessM[Clock](_.get.nanoTime)
```
forces us to provide a clock in the right place.

Now the .provideCustomLayer can add our layer to layer stack and it magically pushes the Random into Names. But it will not do that for the clock, which is required further down, in History2. So the following code does NOT compile:
```
```scala
def wonLastYear2 = testM("won last year") {
for {
team <- teams.pickTeam(5)
Expand Down