Skip to content

Commit

Permalink
- Removed section numbers
Browse files Browse the repository at this point in the history
  • Loading branch information
unnsse committed Oct 9, 2024
1 parent 14123bb commit 3724a00
Showing 1 changed file with 18 additions and 35 deletions.
53 changes: 18 additions & 35 deletions content/posts/typelevel_weather_10032024.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,14 +44,13 @@ repost:
# See details front matter: https://fixit.lruihao.cn/documentation/content-management/introduction/#front-matter
---

# 1.0 Introduction

# Introduction
[Scala](https://www.scala-lang.org) is a general purpose statically typed language that seamlessly blends object-oriented and functional programming.
Its expressive syntax and functional features, along with its JVM compatibility, makes it an attractive choice for
developers. Although it has a steep learning curve, mastering Scala can lead to highly performant and stable software,
especially in concurrent environments. All of this is what caught my eye many years ago when I first came across Scala.

## 1.1. Scala & Typelevel Stack
## Scala & Typelevel Stack
The [Typelevel](https://typelevel.org/) stack is a Scala based ecosystem/framework of modular abstractions that emphasize
functional programming and [Category Theory](https://en.wikipedia.org/wiki/Category_theory).

Expand All @@ -60,8 +59,7 @@ The service retrieves real-time weather information based on latitude and longit
It also provides a high-level overview of how to leverage Typelevel projects to quickly set up a semi-production-ready
JSON parsing HTTP service with integration tests and logging.

# 2.0 Acceptance Criteria

# Acceptance Criteria
The HTTP Weather Service should adhere to the following requirements:

1. Accepts latitude and longitude coordinates.
Expand All @@ -70,17 +68,15 @@ The HTTP Weather Service should adhere to the following requirements:
4. Uses the [National Weather Service API Web Service](https://www.weather.gov/documentation/services-web-api) as a data source.
5. Comprehensive code coverage using `scalatest`.

# 3.0 Implementation

# Implementation
The interesting mix of Typelevel's [http4s](https://http4s.org/), [Cats](https://typelevel.org/cats), and [Cats Effect](https://typelevel.org/cats-effect) libraries proved a different (and yet
a very rewarding) experience due to the functional programming style and mindset enforced. This contrasts immensely with
the traditional Java Spring Boot based Microservices codebases for which I have quite extensive professional experience
designing & developing in. The asynchronous capabilities of `Cats Effect`, combined with the powerful functional
abstractions in `Cats` (e.g. `Monads`, `Applicatives`, `Show`, `Traverse`, etc), have shown me how to fully leverage
Scala with Typelevel for specific distributed backend use cases.

## 3.1 Top-Level Singleton Object

## Top-Level Singleton Object
Started out by using a top level singleton object aptly named `WeatherServer`, to contain every method and expression,
which extends `cats.effect.IOApp`:

Expand All @@ -89,8 +85,7 @@ object WeatherServer extends IOApp {
// All the magic happens in here
}
```
## 3.2 HTTP GET Endpoint

## HTTP GET Endpoint
For the specific HTTP GET endpoint, I used the `http4s` library's DSL convention for the routing:

```scala
Expand All @@ -109,8 +104,7 @@ val weatherService: HttpRoutes[IO] = HttpRoutes.of[IO] {
}
}
```
## 3.3 Valid Coordinates

## Valid Coordinates
In order, to handle the edge case of invalid longitude & latitude coordinates, created the following helper method:

```scala
Expand All @@ -119,8 +113,7 @@ private def isValidCoordinate(lat: Double, lon: Double): Boolean = {
lat >= -90 && lat <= 90 && lon >= -180 && lon <= 180
}
```
## 3.4 Classify Temperature

## Classify Temperature
Wrote the following helper method to categorize the temperature as cold, moderate or hot:

```scala
Expand All @@ -131,8 +124,7 @@ def classifyTemperature(temp: Double): String = {
else "moderate"
}
```
## 3.5 Embedded HTTP Server

## Embedded HTTP Server
Used the embedded `EmberServerBuilder` to serve this HTTP GET endpoint on port 8080:

```scala
Expand All @@ -150,10 +142,8 @@ override def run(args: List[String]): IO[ExitCode] = {
} yield ExitCode.Success
}
```
# 4.0 Compilation & Bootstrap

## 4.1 Compile project using sbt

# Compilation & Bootstrap
## Compile project using sbt
To compile the project, invoke `sbt compile`:

```bash
Expand All @@ -169,8 +159,7 @@ To compile the project, invoke `sbt compile`:
[success] Total time: 0 s, completed Oct 4, 2024, 4:35:11 PM
```
## 4.2 Bootstrap/Run WeatherServer using sbt
## Bootstrap/Run WeatherServer using sbt
To run locally, invoke `sbt run`:
```bash
Expand All @@ -185,8 +174,7 @@ To run locally, invoke `sbt run`:
[info] 00:40:28.776 [io-compute-5] INFO weather.WeatherServer - Starting Weather Server
[info] 00:40:29.067 [io-compute-0] INFO o.h.e.s.EmberServerBuilderCompanionPlatform - Ember-Server service bound to address: [::]:8080
```
## 4.3 HTTP GET Endpoint
## HTTP GET Endpoint
With the HTTP Service running locally, one can hit the HTTP GET endpoint passing in longitude and latitude for San Francisco:
`http://localhost:8080/weather?latitude=37.7749&longitude=-122.4194`
Expand All @@ -200,15 +188,13 @@ Which will return the following real-time weather information in JSON format:
}
```
# 5.0 Testing
# Testing
Also, good software engineering never neglects weeding out all the edge cases through a proper unit/integration test!
A combination of `cats.effect.IO` and `http4s` inside my `scalatest` helped make this test coverage not only
comprehensive but asynchronous:
## 5.1 Using scalatest
## Using scalatest
```scala
package weather
Expand Down Expand Up @@ -249,8 +235,7 @@ class WeatherServerSpec extends AsyncFreeSpec with AsyncIOSpec with Matchers {
// More test cases covered can be found in Github repo source listing (see below)
}
```
## 5.2 Running tests using sbt
## Running tests using sbt
Now, when invoking with `sbt test`, seeing the tests passing was very gratifying (after jumping through a bunch of rabbit
holes, not only for the acceptance criteria, but dealing with the edge/corner cases that arose) as this was my first foray
using the Typelevel stack for creating a working prototype:
Expand All @@ -275,8 +260,7 @@ using the Typelevel stack for creating a working prototype:
[success] Total time: 2 s, completed Oct 3, 2024, 11:58:16 PM
```
# 6.0 Afterthoughts
# Afterthoughts
This was a good learning experience how Scala's Typelevel stack can be used to create an HTTP API which sends requests
to external data sources, parses JSON, and returns asynchronous responses. Also, it demonstrates how to test Typelevel
code using the `scalatest` library.
Expand Down Expand Up @@ -308,8 +292,7 @@ trait WeatherInfo[F[_]] {
By using the `F[_]` higher kind type, you allow the caller to decide which effect type to use (e.g., IO, Future, etc.),
making the trait abstract over different computational effects. More on Tagless Final later...
# 7.0 GitHub Repository
# GitHub Repository
The full code is available here: https://github.com/unnsse/WeatherService
Note: By no means, is this code production ready or even should be considered idiomatic Scala, this was just a mere
Expand Down

0 comments on commit 3724a00

Please sign in to comment.