Skip to content

Commit

Permalink
init
Browse files Browse the repository at this point in the history
  • Loading branch information
veysiertekin committed Dec 1, 2021
0 parents commit 20ac86e
Show file tree
Hide file tree
Showing 93 changed files with 1,971 additions and 0 deletions.
30 changes: 30 additions & 0 deletions .github/workflows/publish-openapi.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
name: publish-openapi
on:
push:
branches:
- main
jobs:
publish:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2.4.0
- name: Setup Scala
uses: olafurpg/setup-scala@v13
with:
java-version: openjdk@1.11.0
- name: Generate OpenApi Spec
working-directory: tapir-akka-http
run: sbt "runMain com.test.example.http_example.OpenApiGenerator $GITHUB_WORKSPACE/openapi.yaml"
- name: Swagger ui action
id: swagger-ui-action
uses: pjoc-team/swagger-ui-action@v0.0.2
with:
dir: ./
pattern: openapi.yaml
- name: Deploy
uses: peaceiris/actions-gh-pages@v3
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_branch: gh-pages
publish_dir: swagger-ui
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
**/.bsp/
**/*.iml
**/.idea
**/target/
97 changes: 97 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
# HTTP Framework Samples in Scala

# Design

All implementations follows below design:

<br><br><br><br>
![Floe diagram](images/flow-diagram.png)
<br><br><br><br>

# Rest implementation: ☕️ Coffee Shop API

All examples are using [coffee-shop](https://github.com/veysiertekin/coffee-shop) static rest service except ZIO Http because of the some bugs. For ZIO, I have implemented a simple [Heroku service described here](https://github.com/veysiertekin/scala-http-framework-samples#simple-remote-service-on-heroku).


|Endpoint|Description|Return type|Request example|Return example|
|---|---|---|---|---|
|/coffee|Returns coffee types|<pre>Array[String]</pre>|http://localhost:9000/coffee|<pre>[<br> "hots",<br> "iced" <br>]</pre>|
|/coffee/#category|Drinks in the category|<pre>Array[<br> Drink(<br> title: String,<br> id: Int,<br> description: String,<br> ingredients: List[String]<br> )<br>]</pre>|http://localhost:9000/coffee/hots|<pre>[<br> {<br> "title": "Black",<br> "id": 1,<br> "description": "Black coffee is as simple as it gets with ground coffee beans steeped in hot water, served warm. And if you want to sound fancy, you can call black coffee by its proper name: cafe noir.",<br> "ingredients": [<br> "Coffee"<br> ]<br> },<br>...</pre>|


## Akka Http

* Functional implementation
* [Native Datadog support](https://docs.datadoghq.com/tracing/setup_overview/compatibility_requirements/java/#web-framework-compatibility)
* Supports full HTTP protocol
* Detailed documentation: [https://doc.akka.io/docs/akka-http/current/index.html](https://doc.akka.io/docs/akka-http/current/index.html)

## Http4s

* Functional implementation
* Support for effect libraries (cats effect etc)
* Limited Datadog support via third party libraries ( [avast/datadog4s](https://github.com/avast/datadog4s), [Kamon](https://github.com/kamon-io/Kamon) etc). Integration in progress: [https://github.com/DataDog/dd-trace-java/issues/1934](https://github.com/DataDog/dd-trace-java/issues/1934)
* Supports full HTTP protocol
* Limited documentation: [https://http4s.org/v0.23/index.html](https://http4s.org/v0.23/index.html)

## ZIO Http

(as of writing this documentation)

* Does not support full Http Protocal
* No native Datadog support yet
* Client has major pitfalls: stable channel does not have Https support. Beta channel has some bugs (removes trailing `/` in the URLS etc)
* Functional, and follows effect library guides
* Limited documentation (explains just a simple example): [https://dream11.github.io/zio-http/docs/index](https://dream11.github.io/zio-http/docs/index)


## Finatra

* Play like dependency framework (Guice) support by default
* Supports full HTTP protocol
* Supports both Twitter's Future or Cats effect for processing
* [Native Datadog support](https://docs.datadoghq.com/tracing/setup_overview/compatibility_requirements/java/#web-framework-compatibility)
* Detailed documentation: [https://twitter.github.io/finatra/user-guide/](https://twitter.github.io/finatra/user-guide/)

# OpenAPI Endpoint

All tapir implementations exposes documentation automatically on API level.

ReadDoc Web UI: [http://localhost:9000/docs](http://localhost:9000/docs)

![Web UI](images/docs-ss.png)

Additionally, `tapir-akka-http` also implements a way of exporting openapi yaml. A Github Action uses this implementation and publishes a static swagger documentation at [http://veysiertekin.github.io/scala-http-framework-samples](http://veysiertekin.github.io/scala-http-framework-samples)

![Swagger UI](images/swagger-ui.png)

# Simple Remote Service on Heroku

(This Heroku service has been used in ZIO Http)

1- Go to [heroku-coffee-shop-service/](heroku-coffee-shop-service) in your terminal.

2- Login to Heroku:

```bash
heroku login
```

3- Create a simple app

```bash
heroku create
git push heroku main
```

4- Start application by scaling it to `1`

```bash
heroku ps:scale web=1
```

4- You can view http location of the service by:

```bash
heroku open
```
1 change: 1 addition & 0 deletions heroku-coffee-shop-service/Procfile
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
web: npm start
7 changes: 7 additions & 0 deletions heroku-coffee-shop-service/app.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"name": "Coffee Shop Service",
"description": "static coffe-shop api",
"repository": "https://github.com/veysiertekin/scala-http-framework-samples",
"logo": "https://cdn.rawgit.com/heroku/node-js-getting-started/main/public/node.svg",
"keywords": ["api","heroku"]
}
9 changes: 9 additions & 0 deletions heroku-coffee-shop-service/index.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

31 changes: 31 additions & 0 deletions heroku-coffee-shop-service/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
{
"name": "node-js-getting-started",
"version": "0.3.0",
"description": "A sample Node.js app using Express 4",
"engines": {
"node": "14.x"
},
"main": "index.js",
"scripts": {
"start": "node index.js",
"test": "node test.js"
},
"dependencies": {
"ejs": "^3.1.5",
"express": "^4.15.2"
},
"devDependencies": {
"got": "^11.3.0",
"tape": "^4.7.0"
},
"repository": {
"type": "git",
"url": "https://github.com/heroku/node-js-getting-started"
},
"keywords": [
"node",
"heroku",
"express"
],
"license": "MIT"
}
Binary file added images/docs-ss.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added images/flow-diagram.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added images/swagger-ui.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions std-akka-http/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
*.class
.idea
target/
18 changes: 18 additions & 0 deletions std-akka-http/build.sbt
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
lazy val akkaHttpVersion = "10.2.7"
lazy val akkaVersion = "2.6.17"

lazy val root = (project in file(".")).
settings(
inThisBuild(List(
organization := "com.test.http_example",
scalaVersion := "2.13.4"
)),
name := "akka-http",
libraryDependencies ++= Seq(
"com.typesafe.akka" %% "akka-http" % akkaHttpVersion,
"com.typesafe.akka" %% "akka-http-spray-json" % akkaHttpVersion,
"com.typesafe.akka" %% "akka-actor-typed" % akkaVersion,
"com.typesafe.akka" %% "akka-stream" % akkaVersion,
"ch.qos.logback" % "logback-classic" % "1.2.3"
)
)
1 change: 1 addition & 0 deletions std-akka-http/project/build.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
sbt.version=1.4.6
1 change: 1 addition & 0 deletions std-akka-http/project/plugins.sbt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
addSbtPlugin("io.spray" % "sbt-revolver" % "0.9.1")
5 changes: 5 additions & 0 deletions std-akka-http/src/main/resources/application.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
sample-http-app {
routes {
ask-timeout = 5s
}
}
18 changes: 18 additions & 0 deletions std-akka-http/src/main/resources/logback.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<configuration>
<appender name="STDOUT" target="System.out" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>[%date{ISO8601}] [%level] [%logger] [%thread] [%X{akkaSource}] - %msg%n</pattern>
</encoder>
</appender>

<appender name="ASYNC" class="ch.qos.logback.classic.AsyncAppender">
<queueSize>1024</queueSize>
<neverBlock>true</neverBlock>
<appender-ref ref="STDOUT"/>
</appender>

<root level="INFO">
<appender-ref ref="ASYNC"/>
</root>

</configuration>
43 changes: 43 additions & 0 deletions std-akka-http/src/main/scala/com/test/example/akkahttp/App.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package com.test.example.akkahttp

import akka.actor.CoordinatedShutdown
import akka.actor.typed.ActorSystem
import akka.actor.typed.scaladsl.Behaviors
import akka.http.scaladsl.Http
import akka.http.scaladsl.server.Route
import scala.util.{Failure, Success}

object App {
case object UserInitiatedShutdown extends CoordinatedShutdown.Reason

private def startHttpServer(routes: Route)(implicit system: ActorSystem[_]): Unit = {
import system.executionContext

val futureBinding = Http()
.newServerAt("0.0.0.0", 9000)
.bind(routes)

futureBinding.onComplete {
case Success(binding) =>
val address = binding.localAddress
system.log.info("Server online at http://{}:{}/", address.getHostString, address.getPort)
case Failure(ex) =>
system.log.error("Failed to bind HTTP endpoint, terminating system", ex)
system.terminate()
}
}

def main(args: Array[String]): Unit = {
val rootBehavior = Behaviors.setup[Nothing] { context =>
val coffeeShopActor = context.spawn(CoffeeShopActor(), "CoffeeShopActor")
context.watch(coffeeShopActor)

val routes = new CoffeeRoutes(coffeeShopActor)(context.system)
startHttpServer(routes.routes)(context.system)
Behaviors.empty
}

val system = ActorSystem[Nothing](rootBehavior, "SampleServer")
//CoordinatedShutdown(system).run(UserInitiatedShutdown)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package com.test.example.akkahttp

import akka.actor.typed.scaladsl.AskPattern._
import akka.actor.typed.{ActorRef, ActorSystem}
import akka.http.scaladsl.model.StatusCodes
import akka.http.scaladsl.server.Directives._
import akka.http.scaladsl.server.Route
import akka.util.Timeout
import com.test.example.akkahttp.CoffeeShopActor._
import scala.concurrent.Future

class CoffeeRoutes(actor: ActorRef[CoffeeShopActor.Command])(implicit val system: ActorSystem[_]) extends JsonSupport {
private implicit val timeout: Timeout = Timeout.create(system.settings.config.getDuration("sample-http-app.routes.ask-timeout"))

def getTypes: Future[Array[String]] = actor askWithStatus GetTypes

def getDrinks(coffeeType: String): Future[Array[Drink]] = actor askWithStatus { replyTo =>
GetDrinks(coffeeType, replyTo)
}

val routes: Route =
pathPrefix("coffee") {
concat(
pathEnd {
get {
onSuccess(getTypes) { types =>
complete(StatusCodes.OK, types)
}
}
},
path(Segment) { coffeeType =>
onSuccess(getDrinks(coffeeType)) { types =>
complete(StatusCodes.OK, types)
}
}
)
}
}
Loading

0 comments on commit 20ac86e

Please sign in to comment.