-
Notifications
You must be signed in to change notification settings - Fork 18
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
(example): Provide example application #27
Merged
Merged
Changes from 20 commits
Commits
Show all changes
21 commits
Select commit
Hold shift + click to select a range
7633b64
Provide example application
drmarjanovic 45087a5
Improvements
drmarjanovic 5d2067c
Provide mapping
drmarjanovic 741f4c5
Implement endpoints
drmarjanovic 384fbb0
Provide Postman collection
drmarjanovic 2914510
Update Postman collection
drmarjanovic 53830d0
Fix application.conf
drmarjanovic 9b51f09
Init Elasticsearch on app startup
drmarjanovic ce3cb60
Add README
drmarjanovic be1d7ae
Fix compile
drmarjanovic 3008d82
Address code remarks
drmarjanovic fd74c10
Provide sbt resolver plugin
drmarjanovic 9e0ff48
Cleanup code
drmarjanovic 2685e49
Address code remarks
drmarjanovic 1617858
Address code remarks
drmarjanovic 2b4d12d
Change routing usage
drmarjanovic f8e732e
Improve service pattern
drmarjanovic 0c19b3e
Format code
drmarjanovic ae063a2
Improve creating RepositoriesElasticsearch layer
drmarjanovic 6e6489d
Remove unused environment value
drmarjanovic 3ecefd6
Address code remarks
drmarjanovic File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
# ZIO Elasticsearch Example Application | ||
|
||
This application represents an example of usage `zio-elasticsearch` library for **Elasticsearch 7.x**. | ||
|
||
### Running | ||
|
||
- Run the Elasticsearch 7.x service (one can be found as part of the `docker-compose.yml` file in the root of this | ||
repository) | ||
- Start the application by running the following command: | ||
```shell | ||
./sbt "~example/reStart" | ||
``` | ||
- Check whether the application is running [here](http://localhost:9000/health) | ||
- Explore endpoints using Postman collection (`zio-elasticsearch-example.postman_collection.json`) | ||
|
||
### Description | ||
|
||
On the application startup - a **"repositories"** index will be deleted, and immediately re-created with the mapping | ||
definition given in the `resources/mapping.json` file. | ||
|
||
After successfully starting the application, you can test the exposed ZIO Elasticsearch library's API through exposed HTTP | ||
endpoints. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
http { | ||
port = 9000 | ||
} | ||
|
||
elasticsearch { | ||
host = "localhost" | ||
port = 9200 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
{ | ||
"settings": { | ||
"index": { | ||
"number_of_shards": 1 | ||
} | ||
}, | ||
"mappings": { | ||
"_routing": { | ||
"required": true | ||
}, | ||
"properties": { | ||
"id": { | ||
"type": "keyword" | ||
}, | ||
"organization": { | ||
"type": "keyword" | ||
}, | ||
"name": { | ||
"type": "keyword" | ||
}, | ||
"url": { | ||
"type": "keyword" | ||
}, | ||
"description": { | ||
"type": "text" | ||
}, | ||
"lastCommitAt": { | ||
"type": "date" | ||
}, | ||
"stars": { | ||
"type": "integer" | ||
}, | ||
"forks": { | ||
"type": "integer" | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
package example | ||
|
||
import zio.json.{DeriveJsonEncoder, JsonEncoder} | ||
import zio.schema.{DeriveSchema, Schema} | ||
|
||
import java.time.LocalDateTime | ||
|
||
final case class GitHubRepo( | ||
id: Option[String], | ||
organization: String, | ||
name: String, | ||
url: String, | ||
description: Option[String], | ||
lastCommitAt: LocalDateTime, | ||
stars: Int, | ||
forks: Int | ||
) | ||
|
||
object GitHubRepo { | ||
implicit val schema: Schema[GitHubRepo] = DeriveSchema.gen[GitHubRepo] | ||
|
||
implicit val encoder: JsonEncoder[GitHubRepo] = DeriveJsonEncoder.gen[GitHubRepo] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
package example | ||
|
||
import example.api.{HealthCheck, Repositories} | ||
import example.config.{AppConfig, ElasticsearchConfig, HttpConfig} | ||
import sttp.client3.httpclient.zio.HttpClientZioBackend | ||
import zio._ | ||
import zio.config.getConfig | ||
import zio.elasticsearch.{ElasticConfig, ElasticExecutor, ElasticRequest} | ||
import zio.http.{Server, ServerConfig} | ||
|
||
import scala.io.Source | ||
|
||
object Main extends ZIOAppDefault { | ||
|
||
override def run: Task[ExitCode] = { | ||
val elasticConfigLive = ZLayer(getConfig[ElasticsearchConfig].map(es => ElasticConfig(es.host, es.port))) | ||
|
||
(prepare *> runServer).provide( | ||
AppConfig.live, | ||
elasticConfigLive, | ||
ElasticExecutor.live, | ||
HttpClientZioBackend.layer() | ||
) | ||
} | ||
|
||
private[this] def prepare: RIO[ElasticExecutor, Unit] = { | ||
val deleteIndex: RIO[ElasticExecutor, Unit] = | ||
for { | ||
_ <- ZIO.logInfo(s"Deleting index '$Index'...") | ||
_ <- ElasticRequest.deleteIndex(Index).execute | ||
} yield () | ||
|
||
val createIndex: RIO[ElasticExecutor, Unit] = | ||
for { | ||
_ <- ZIO.logInfo(s"Creating index '$Index'...") | ||
mapping <- ZIO.attempt(Source.fromURL(getClass.getResource("/mapping.json")).mkString) | ||
_ <- ElasticRequest.createIndex(Index, Some(mapping)).execute | ||
} yield () | ||
|
||
deleteIndex *> createIndex | ||
} | ||
|
||
private[this] def runServer: RIO[HttpConfig with ElasticExecutor, ExitCode] = { | ||
val serverConfigLive = ZLayer(getConfig[HttpConfig].map(http => ServerConfig.default.port(http.port))) | ||
|
||
(for { | ||
http <- getConfig[HttpConfig] | ||
_ <- ZIO.logInfo(s"Starting an HTTP service on port: ${http.port}") | ||
routes = HealthCheck.Route ++ Repositories.Routes | ||
_ <- Server.serve(routes) | ||
} yield ExitCode.success).provideSome( | ||
RepositoriesElasticsearch.live, | ||
Server.live, | ||
serverConfigLive | ||
) | ||
} | ||
|
||
} |
55 changes: 55 additions & 0 deletions
55
modules/example/src/main/scala/example/RepositoriesElasticsearch.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
package example | ||
|
||
import zio.elasticsearch.ElasticError.DocumentRetrievingError.DocumentNotFound | ||
import zio.elasticsearch.{DocumentId, ElasticExecutor, ElasticRequest, Routing} | ||
import zio.{RIO, Task, URLayer, ZIO, ZLayer} | ||
|
||
final case class RepositoriesElasticsearch(executor: ElasticExecutor) { | ||
|
||
def findById(organization: String, id: String): Task[Option[GitHubRepo]] = | ||
for { | ||
routing <- Routing.make(organization).toZIO.mapError(e => new IllegalArgumentException(e)) | ||
req = ElasticRequest.getById[GitHubRepo](Index, DocumentId(id)).routing(routing) | ||
res <- executor.execute(req) | ||
} yield res.toOption | ||
|
||
def create(repository: GitHubRepo): Task[Option[DocumentId]] = | ||
for { | ||
routing <- Routing.make(repository.organization).toZIO.mapError(e => new IllegalArgumentException(e)) | ||
req = ElasticRequest.create(Index, repository).routing(routing) | ||
res <- executor.execute(req) | ||
} yield res | ||
|
||
def upsert(id: String, repository: GitHubRepo): Task[Unit] = | ||
for { | ||
routing <- Routing.make(repository.organization).toZIO.mapError(e => new IllegalArgumentException(e)) | ||
req = ElasticRequest.upsert(Index, DocumentId(id), repository).routing(routing) | ||
_ <- executor.execute(req) | ||
} yield () | ||
|
||
def remove(organization: String, id: String): Task[Either[DocumentNotFound.type, Unit]] = | ||
for { | ||
routing <- Routing.make(organization).toZIO.mapError(e => new IllegalArgumentException(e)) | ||
req = ElasticRequest.deleteById(Index, DocumentId(id)).routing(routing) | ||
res <- executor.execute(req) | ||
} yield res | ||
|
||
} | ||
|
||
object RepositoriesElasticsearch { | ||
|
||
def findById(organization: String, id: String): RIO[RepositoriesElasticsearch, Option[GitHubRepo]] = | ||
ZIO.serviceWithZIO[RepositoriesElasticsearch](_.findById(organization, id)) | ||
|
||
def create(repository: GitHubRepo): RIO[RepositoriesElasticsearch, Option[DocumentId]] = | ||
ZIO.serviceWithZIO[RepositoriesElasticsearch](_.create(repository)) | ||
|
||
def upsert(id: String, repository: GitHubRepo): RIO[RepositoriesElasticsearch, Unit] = | ||
ZIO.serviceWithZIO[RepositoriesElasticsearch](_.upsert(id, repository)) | ||
|
||
def remove(organization: String, id: String): RIO[RepositoriesElasticsearch, Either[DocumentNotFound.type, Unit]] = | ||
ZIO.serviceWithZIO[RepositoriesElasticsearch](_.remove(organization, id)) | ||
|
||
lazy val live: URLayer[ElasticExecutor, RepositoriesElasticsearch] = | ||
ZLayer.fromFunction(RepositoriesElasticsearch(_)) | ||
} |
13 changes: 13 additions & 0 deletions
13
modules/example/src/main/scala/example/api/HealthCheck.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
package example.api | ||
|
||
import zio.http._ | ||
import zio.http.model.Method | ||
import zio.json.ast.Json._ | ||
|
||
object HealthCheck { | ||
|
||
final val Route: Http[Any, Nothing, Any, Response] = Http.collect { case Method.GET -> !! / "health" => | ||
Response.json(Obj("name" -> Str("zio-elasticsearch-example"), "status" -> Str("up")).toJson) | ||
} | ||
|
||
} |
86 changes: 86 additions & 0 deletions
86
modules/example/src/main/scala/example/api/Repositories.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
package example.api | ||
|
||
import example.{GitHubRepo, RepositoriesElasticsearch} | ||
import zio.ZIO | ||
import zio.elasticsearch.DocumentId | ||
import zio.http._ | ||
import zio.http.model.Method | ||
import zio.http.model.Status._ | ||
import zio.json.EncoderOps | ||
import zio.schema.codec.JsonCodec | ||
|
||
object Repositories { | ||
|
||
private final val BasePath = !! / "api" / "repositories" | ||
|
||
final val Routes: Http[RepositoriesElasticsearch, Nothing, Request, Response] = | ||
Http.collectZIO[Request] { | ||
case Method.GET -> BasePath => | ||
ZIO.succeed(Response.text("TODO: Get a list of repositories").setStatus(NotImplemented)) | ||
|
||
case Method.GET -> BasePath / organization / id => | ||
RepositoriesElasticsearch | ||
.findById(organization, id) | ||
.map { | ||
case Some(r) => | ||
Response.json(r.toJson) | ||
case None => | ||
Response.json(ErrorResponse.fromReasons(s"Repository $id does not exist.").toJson).setStatus(NotFound) | ||
} | ||
.orDie | ||
|
||
case req @ Method.POST -> BasePath => | ||
req.body.asString | ||
.map(JsonCodec.JsonDecoder.decode[GitHubRepo](GitHubRepo.schema, _)) | ||
.flatMap { | ||
case Left(e) => | ||
ZIO.succeed(Response.json(ErrorResponse.fromReasons(e.message).toJson).setStatus(BadRequest)) | ||
case Right(repo) => | ||
RepositoriesElasticsearch.create(repo).map { | ||
case Some(id) => | ||
Response.json(repo.copy(id = Some(DocumentId.unwrap(id))).toJson).setStatus(Created) | ||
case None => | ||
Response.json(ErrorResponse.fromReasons("Failed to create repository.").toJson).setStatus(BadRequest) | ||
} | ||
} | ||
.orDie | ||
|
||
case req @ Method.PUT -> BasePath / id => | ||
req.body.asString | ||
.map(JsonCodec.JsonDecoder.decode[GitHubRepo](GitHubRepo.schema, _)) | ||
.flatMap { | ||
case Left(e) => | ||
ZIO.succeed(Response.json(ErrorResponse.fromReasons(e.message).toJson).setStatus(BadRequest)) | ||
case Right(repo) if repo.id.exists(_ != id) => | ||
ZIO.succeed( | ||
Response | ||
.json( | ||
ErrorResponse.fromReasons("The ID provided in the path does not match the ID from the body.").toJson | ||
) | ||
.setStatus(BadRequest) | ||
) | ||
case Right(repo) => | ||
(RepositoriesElasticsearch | ||
.upsert(id, repo.copy(id = Some(id))) *> RepositoriesElasticsearch.findById( | ||
repo.organization, | ||
id | ||
)).map { | ||
case Some(updated) => Response.json(updated.toJson) | ||
case None => Response.json(ErrorResponse.fromReasons("Operation failed.").toJson).setStatus(BadRequest) | ||
} | ||
} | ||
.orDie | ||
|
||
case Method.DELETE -> BasePath / organization / id => | ||
RepositoriesElasticsearch | ||
.remove(organization, id) | ||
.map { | ||
case Right(_) => | ||
Response.status(NoContent) | ||
case Left(_) => | ||
Response.json(ErrorResponse.fromReasons(s"Repository $id does not exist.").toJson).setStatus(NotFound) | ||
} | ||
.orDie | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
package example | ||
|
||
import zio.Chunk | ||
import zio.json._ | ||
|
||
package object api { | ||
|
||
final case class ErrorResponseData(body: Chunk[String]) | ||
|
||
object ErrorResponseData { | ||
implicit val encoder: JsonEncoder[ErrorResponseData] = DeriveJsonEncoder.gen[ErrorResponseData] | ||
} | ||
|
||
final case class ErrorResponse(errors: ErrorResponseData) | ||
|
||
object ErrorResponse { | ||
implicit val encoder: JsonEncoder[ErrorResponse] = DeriveJsonEncoder.gen[ErrorResponse] | ||
|
||
def fromReasons(reasons: String*): ErrorResponse = | ||
new ErrorResponse(ErrorResponseData(Chunk.fromIterable(reasons))) | ||
} | ||
|
||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe we could extract this to method
makeRouting
, because this code is repeated in every method.