Skip to content
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

[WIP] Topic impl backend play scala, closes #16 #17

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
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
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
.idea/*
.idea
*.iml
8 changes: 8 additions & 0 deletions backend-play-scala/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
logs
target
/.idea
/.idea_modules
/.classpath
/.project
/.settings
/RUNNING_PID
49 changes: 49 additions & 0 deletions backend-play-scala/README
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
This is your new Play application
=================================

This file will be packaged with your application when using `activator dist`.

There are several demonstration files available in this template.

Controllers
===========

- HomeController.scala:

Shows how to handle simple HTTP requests.

- AsyncController.scala:

Shows how to do asynchronous programming when handling a request.

- CountController.scala:

Shows how to inject a component into a controller and use the component when
handling requests.

Components
==========

- Module.scala:

Shows how to use Guice to bind all the components needed by your application.

- Counter.scala:

An example of a component that contains state, in this case a simple counter.

- ApplicationTimer.scala:

An example of a component that starts when the application starts and stops
when the application stops.

Filters
=======

- Filters.scala:

Creates the list of HTTP filters used by your application.

- ExampleFilter.scala

A simple filter that adds a header to every response.
19 changes: 19 additions & 0 deletions backend-play-scala/README_ja.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
backend-play-scala
===

play-scala によるバックエンド実装。

Get start
---
TBD

### Requirements
- JDK 1.8.x
- Scala 2.11.7


Development
---

### IntelliJ
`File` -> `Open` -> Open File or Project `path/to/beer-driven-development/backend-play-scala`
33 changes: 33 additions & 0 deletions backend-play-scala/app/Filters.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import javax.inject._
import play.api._
import play.api.http.HttpFilters
import play.api.mvc._

import filters.ExampleFilter

/**
* This class configures filters that run on every request. This
* class is queried by Play to get a list of filters.
*
* Play will automatically use filters from any class called
* `Filters` that is placed the root package. You can load filters
* from a different class by adding a `play.http.filters` setting to
* the `application.conf` configuration file.
*
* @param env Basic environment settings for the current application.
* @param exampleFilter A demonstration filter that adds a header to
* each response.
*/
@Singleton
class Filters @Inject() (
env: Environment,
exampleFilter: ExampleFilter) extends HttpFilters {

override val filters = {
// Use the example filter if we're running development mode. If
// we're running in production or test mode then don't use any
// filters at all.
if (env.mode == Mode.Dev) Seq(exampleFilter) else Seq.empty
}

}
28 changes: 28 additions & 0 deletions backend-play-scala/app/Module.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import com.google.inject.AbstractModule
import java.time.Clock

import services.{ApplicationTimer, DiceServiceImpl, DiceService}

/**
* This class is a Guice module that tells Guice how to bind several
* different types. This Guice module is created when the Play
* application starts.

* Play will automatically use any class called `Module` that is in
* the root package. You can create modules in other locations by
* adding `play.modules.enabled` settings to the `application.conf`
* configuration file.
*/
class Module extends AbstractModule {

override def configure() = {
// Use the system clock as the default implementation of Clock
bind(classOf[Clock]).toInstance(Clock.systemDefaultZone)
// Ask Guice to create an instance of ApplicationTimer when the
// application starts.
bind(classOf[ApplicationTimer]).asEagerSingleton()
// Set AtomicCounter as the implementation for Counter.
bind(classOf[DiceService]).to(classOf[DiceServiceImpl])
}

}
18 changes: 18 additions & 0 deletions backend-play-scala/app/com/github/aosn/play/ValidationError.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.github.aosn.play

import play.api.data.FormError

object ValidationError {

/** Seq[FormError]をタプルのリストに */
def mkErrorString[A](errors: Seq[FormError]): Seq[(String, String)] = {
errors.map {
case e if e.message.contains(": ") =>
e.message.split(": ", 2) match {
case Array(key, message) => (key, message)
}
case e =>
(e.key, e.message.toString)
}
}
}
28 changes: 28 additions & 0 deletions backend-play-scala/app/controllers/BaseController.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package controllers

import com.github.aosn.play.ValidationError
import play.api.data.Form
import play.api.mvc.Result
import play.api.mvc.Results._
import util.JsonSerializer

import scala.util.Try

trait BaseController {

def validationError[A](form: Form[A]): Result =
validationError(ValidationError.mkErrorString(form.errors))

def validationError(results: Seq[(String, String)]): Result = {
BadRequest(JsonSerializer.serialize("errors" -> results.toMap))
}

def okJson(res: AnyRef): Result = {
Ok(JsonSerializer.serialize(res))
}

def okResult[T <: AnyRef](a: Try[T]): Result = {
okJson(a.get)
}

}
17 changes: 17 additions & 0 deletions backend-play-scala/app/controllers/DiceController.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package controllers

import javax.inject._

import play.api.mvc._
import services.DiceService

@Singleton
class DiceController @Inject()(service: DiceService) extends Controller with BaseController {

def roll(gameId: Int) = Action { implicit req =>
DiceForm.roll.bindFromRequest.fold(validationError[DiceRollParam], params => {
okResult(service.roll(gameId, params))
})
}

}
14 changes: 14 additions & 0 deletions backend-play-scala/app/controllers/DiceForm.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package controllers

import play.api.data.Form
import play.api.data.Forms._

object DiceForm {
def roll = Form(
mapping(
"times" -> number(min = 1)
)(DiceRollParam.apply)(DiceRollParam.unapply)
)
}

case class DiceRollParam(times: Int)
14 changes: 14 additions & 0 deletions backend-play-scala/app/controllers/EntryController.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package controllers

import javax.inject._

import play.api.mvc._

@Singleton
class EntryController @Inject() extends Controller {

def index = Action {
Ok("Ok")
}

}
17 changes: 17 additions & 0 deletions backend-play-scala/app/controllers/GameController.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package controllers

import javax.inject.{Inject, Singleton}

import play.api.mvc.{Action, Controller}
import services.GameService

@Singleton
class GameController @Inject()(service: GameService) extends Controller with BaseController {

def create() = Action { implicit req =>
Ok("")
}

def show(gameId: Int) = ???
def update(gameId: Int) = ???
}
33 changes: 33 additions & 0 deletions backend-play-scala/app/filters/ExampleFilter.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package filters

import akka.stream.Materializer
import javax.inject._
import play.api.mvc._
import scala.concurrent.{ExecutionContext, Future}

/**
* This is a simple filter that adds a header to all requests. It's
* added to the application's list of filters by the
* [[Filters]] class.
*
* @param mat This object is needed to handle streaming of requests
* and responses.
* @param exec This class is needed to execute code asynchronously.
* It is used below by the `map` method.
*/
@Singleton
class ExampleFilter @Inject()(
implicit override val mat: Materializer,
exec: ExecutionContext) extends Filter {

override def apply(nextFilter: RequestHeader => Future[Result])
(requestHeader: RequestHeader): Future[Result] = {
// Run the next filter in the chain. This will call other filters
// and eventually call the action. Take the result and modify it
// by adding a new header.
nextFilter(requestHeader).map { result =>
result.withHeaders("X-ExampleFilter" -> "foo")
}
}

}
39 changes: 39 additions & 0 deletions backend-play-scala/app/services/ApplicationTimer.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package services

import java.time.{Clock, Instant}
import javax.inject._
import play.api.Logger
import play.api.inject.ApplicationLifecycle
import scala.concurrent.Future

/**
* This class demonstrates how to run code when the
* application starts and stops. It starts a timer when the
* application starts. When the application stops it prints out how
* long the application was running for.
*
* This class is registered for Guice dependency injection in the
* [[Module]] class. We want the class to start when the application
* starts, so it is registered as an "eager singleton". See the code
* in the [[Module]] class to see how this happens.
*
* This class needs to run code when the server stops. It uses the
* application's [[ApplicationLifecycle]] to register a stop hook.
*/
@Singleton
class ApplicationTimer @Inject() (clock: Clock, appLifecycle: ApplicationLifecycle) {

// This code is called when the application starts.
private val start: Instant = clock.instant
Logger.info(s"ApplicationTimer demo: Starting application at $start.")

// When the application starts, register a stop hook with the
// ApplicationLifecycle object. The code inside the stop hook will
// be run when the application stops.
appLifecycle.addStopHook { () =>
val stop: Instant = clock.instant
val runningTime: Long = stop.getEpochSecond - start.getEpochSecond
Logger.info(s"ApplicationTimer demo: Stopping application at ${clock.instant} after ${runningTime}s.")
Future.successful(())
}
}
22 changes: 22 additions & 0 deletions backend-play-scala/app/services/DiceService.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package services

import javax.inject._

import controllers.DiceRollParam

import scala.util.{Random, Success, Try}

trait DiceService {
def roll(gameId: Int, params: DiceRollParam): Try[DiceRollResult]
}

@Singleton
class DiceServiceImpl @Inject()(random: Random) extends DiceService {

override def roll(gameId: Int, params: DiceRollParam): Try[DiceRollResult] = {
Success(DiceRollResult(Array.fill(params.times)(random.nextInt(5) + 1)))
}

}

case class DiceRollResult(dice: Array[Int])
10 changes: 10 additions & 0 deletions backend-play-scala/app/services/GameService.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package services

import javax.inject._

trait GameService {
}

@Singleton
class GameServiceImpl extends GameService {
}
20 changes: 20 additions & 0 deletions backend-play-scala/app/util/JsonSerializer.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package util

import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.module.scala.DefaultScalaModule
import com.fasterxml.jackson.module.scala.experimental.ScalaObjectMapper

import scala.util.Try

object JsonSerializer {
private[this] val MAPPER = new ObjectMapper with ScalaObjectMapper
MAPPER.registerModule(DefaultScalaModule)

def serialize(o: AnyRef): String = {
MAPPER.writeValueAsString(o)
}

def deserialize[T](json: String): Try[T] = {
Try(MAPPER.readValue(json))
}
}
Loading