@JsonIgnoreProperties(Array("_isPersisted")) case class Customer(id: String, firstName: String, lastName: String, email: Option[String], birthday: Option[Date]) extends KeyedEntity[String]
Option[_]
correspond to nullable columns of the database. Such fields can take two kinds of values: Some(value)
, if the value is, and None
, if it is not. Using Option
allows you to minimize the chances of a NullPointerException
and is common practice in functional programming languages (especially in those in which there is no concept of null
at all).@JsonIgnoreProperties
excludes certain fields from JSON serialization. In this case, the _isPersisted
field, which Squeryl added, had to be excluded. Class.forName("org.h2.Driver") SessionFactory.concreteFactory = Some(() => Session.create(DriverManager.getConnection("jdbc:h2:test", "sa", ""), new H2Adapter))
object DB extends Schema { val customer = table[Customer] } transaction { allCatch opt DB.create }
Customer
class, and then execute the DDL commands to create this table. In real life, using automatic table creation is usually problematic, but for quick demonstration it is very convenient. If tables in the database already exist, DB.create
throw an exception, which we, thanks to allCatch opt
, successfully ignore. val mapper = new ObjectMapper().withModule(DefaultScalaModule)
def parseCustomerJson(json: String): Option[Customer] = allCatch opt mapper.readValue(json, classOf[Customer]) def readCustomer(req: HttpRequest[_], id: => String): Option[Customer] = parseCustomerJson(Body.string(req)) map (_.copy(id = id))
parseCustomerJson
function parseCustomerJson
JSON. Thanks to the use of allCatch opt
exceptions that occurred during the parsing process will be intercepted and as a result we will get None
. The second function, readCustomer
, is directly related to the processing of an HTTP request — it reads the request body, turns it into an object of type Customer
and sets the id
field to the specified value.Customer
object (or List[Customer]
) into an HTTP response body — is also not difficult: case class ResponseJson(o: Any) extends ComposeResponse( ContentType("application/json") ~> ResponseString(mapper.writeValueAsString(o)))
ResponseJson
, and the Unfiltered framework will take care of turning it into the correct HTTP response. def nextId = UUID.randomUUID().toString
val service = cycle.Planify { case /* */ => /* , */ }
/customer
and /customer/[id]
. Let's start with the second: case req@Path(Seg("customer" :: id :: Nil)) => req match { case GET(_) => transaction { DB.customer.lookup(id) cata(ResponseJson, NotFound) } case PUT(_) => transaction { readCustomer(req, id) ∘ DB.customer.update cata(_ => Ok, BadRequest) } case DELETE(_) => transaction { DB.customer.delete(id); NoContent } case _ => Pass }
/customer/[id]
and binds the passed identifier to the id variable (if the immutable variable can be called that at all). In the following lines, we specify the behavior depending on the type of request. Let us analyze, for example, the processing of the PUT method in steps:transaction { ... }
: we indicate that while the handler's body is running, a transaction should be opened,readCustomer(req, id)
: use a previously written method that reads the request body and returns Option[Customer]
∘
: this symbol deserves special attention, in fact, it is synonymous with the map operation and allows you to apply any function to the contents of the Option, if the content isDB.customer.update
: the same function that we want to use is an update of the entity in the database,cata(_ => Ok, BadRequest)
: returns Ok
if Option
has a value or BadRequest
if the request could not be parsed and we have None
instead of the client./customer
, we will need two auxiliary functions: val field: PartialFunction[String, Customer => TypedExpressionNode[_]] = { case "id" => _.id case "firstName" => _.firstName case "lastName" => _.lastName case "email" => _.email case "birthday" => _.birthday } val ordering: PartialFunction[String, TypedExpressionNode[_] => OrderByExpression] = { case "asc" => _.asc case "desc" => _.desc }
order by
part of the request and, most likely, having rummaged in the depths of Squeryl, they could have been written more simply, but this also suited me. Handler code itself: case req@Path(Seg("customer" :: Nil)) => req match { case POST(_) => transaction { readCustomer(req, nextId) ∘ DB.customer.insert ∘ ResponseJson cata(_ ~> Created, BadRequest) } case GET(_) & Params(params) => transaction { import Params._ val orderBy = (params.get("orderby") ∗ first orElse Some("id")) ∗ field.lift val order = (params.get("order") ∗ first orElse Some("asc")) ∗ ordering.lift val pageNum = params.get("pagenum") ∗ (first ~> int) val pageSize = params.get("pagesize") ∗ (first ~> int) val offset = ^(pageNum, pageSize)(_ * _) val query = from(DB.customer) { q => select(q) orderBy ^(orderBy, order)(_ andThen _ apply q).toList } val pagedQuery = ^(offset, pageSize)(query.page) getOrElse query ResponseJson(pagedQuery.toList) } case _ => Pass }
∗
and ^
. The first (neatly, do not confuse it with the usual asterisk *
) is a synonym for flatMap
and differs from map
in that the function used must also return Option
. Thus, we can consistently perform several operations, each of which either returns a value successfully or returns None
in case of an error. The second operator is a bit more complicated and allows you to perform some operation only if all the variables used are not equal to None
. This allows us to perform sorting only if both the column and the direction are indicated, and split the result into pages only if both the page number and its size are specified. Http(8080).plan(service).run()
scala.Either
or scalaz.Validation
for error scalaz.Validation
, and someone might not like the use of Unicode operators. In addition, quite complicated operations can sometimes be hidden behind external simplicity, and in order to understand how everything is arranged “under the hood”, it is necessary to tighten the convolutions. Nevertheless, I hope that this article will encourage someone to take a closer look at Scala: even if you fail to apply this language in your work, you will surely learn something new.flatMap
(aka ∗
) is a monadic bind, and the operator ^
is directly related to applicative functors.Source: https://habr.com/ru/post/150199/
All Articles