diff --git a/_pages/zio-streams/zio-kafka-part1.md b/_pages/zio-streams/zio-kafka-part1.md index 6f295be..48a82c2 100644 --- a/_pages/zio-streams/zio-kafka-part1.md +++ b/_pages/zio-streams/zio-kafka-part1.md @@ -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": { @@ -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 -``` \ No newline at end of file +``` diff --git a/_pages/zio-uzhttp-sttp/uzhttp-sttp.md b/_pages/zio-uzhttp-sttp/uzhttp-sttp.md index 9fdcbaf..1af3fa8 100644 --- a/_pages/zio-uzhttp-sttp/uzhttp-sttp.md +++ b/_pages/zio-uzhttp-sttp/uzhttp-sttp.md @@ -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. @@ -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 @@ -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 @@ -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. @@ -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 @@ -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 @@ -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 @@ -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")) @@ -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 @@ -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 } diff --git a/_pages/zlayer/Examples.md b/_pages/zlayer/Examples.md index a0d18ea..f39e963 100644 --- a/_pages/zlayer/Examples.md +++ b/_pages/zlayer/Examples.md @@ -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 @@ -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 = @@ -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] = ??? ``` @@ -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 { @@ -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)