研究关于restapi的初衷是想搞一套通用的平台数据表维护http工具。前面谈过身份验证和使用权限、文件的上传下载,这次来到具体的数据库表维护。我们在这篇示范里设计一套通用的对平台每一个数据表的标准维护方式。http服务端数据表维护CRUD有几个标准的部分组成:Model,Repository,Route。我们先看看这几个类型的基类:
trait ModelBase[M,E] { def to: M => E def from: E => M } trait RepoBase[M] { def getById(id: Long) : Future[Option[M]] def getAll : Future[Seq[M]] def filter(expr: M => Boolean): Future[Seq[M]] def save(row: M) : Future[AnyRef] def deleteById(id: Long) : Future[Int] def updateById(id: Long, row: M) : Future[Int] } abstract class RouteBase[M](val pathName: String, repository: RepoBase[M])( implicit m: Manifest[M]) extends Directives with JsonConverter { val route = path(pathName) { get { complete(futureToJson(repository.getAll)) } ~ post { entity(as[String]) { json => val extractedEntity = fromJson[M](json) complete(futureToJson(repository.save(extractedEntity))) } } } ~ path(pathName / LongNumber) { id =>
get { complete(futureToJson(repository.getById(id))) } ~ put { entity(as[String]) { json => val extractedEntity = fromJson[M](json) complete(futureToJsonAny(repository.updateById(id, extractedEntity))) } } ~ delete { complete(futureToJsonAny(repository.deleteById(id))) } } }
很明显,Model是数据库表行类型的表达方式、Repository是数据库表操作方法、Route是操作方法的调用。下面是这几个类型的实例示范:
object MockModels { case class DataRow ( name: String, age: Int ) case class Person(name: String, age: Int) extends ModelBase[Person,DataRow] { def to: Person => DataRow = p => DataRow ( name = p.name, age = p.age ) def from: DataRow => Person = m => Person( name = m.name, age = m.age ) } } package com.datatech.restapi import MockModels._ import scala.concurrent.Future object MockRepo { class PersonRepo extends RepoBase[Person] { override def getById(id: Long): Future[Option[Person]] = Future.successful(Some(Person("johnny lee",23))) override def getAll: Future[Seq[Person]] = Future.successful( Seq(Person("jonny lee",23),Person("candy wang",45),Person("jimmy kowk",34)) ) override def filter(expr: Person => Boolean): Future[Seq[Person]] = Future.successful( Seq(Person("jonny lee",23),Person("candy wang",45),Person("jimmy kowk",34)) ) override def save(row: Person): Future[Person] = Future.successful(row) override def deleteById(id: Long): Future[Int] = Future.successful(1) override def updateById(id: Long, row: Person): Future[Int] = Future.successful(1) } } object PersonRoute { class PersonRoute(pathName: String, repo: RepoBase[Person]) extends RouteBase[Person](pathName,repo) val route = new PersonRoute("person",new PersonRepo).route }
Model代表数据表结构以及某种数据库的表行与Model之间的转换。而repository则代表某种数据库对库表具体操作的实现。我们把焦点拉回到RouteBase上来,这里包含了rest标准的get,post,put,delete http操作。实际上就是request/response处理机制。因为数据需要在线上on-the-wire来回移动,所以需要进行数据转换。通用的数据传输模式是:类->json->类,即序列化/反序列化。akka-http提供了丰富的Marshaller来实现自动的数据转换,但在编译时要提供Marshaller的隐式实例implicit instance,所以用类参数是无法通过编译的。只能手工进行类和json之间的转换。json转换是通过json4s实现的:
import java.text.SimpleDateFormat import akka.http.scaladsl.model._ import org.json4s.JsonAST.{JNull, JString} import org.json4s.{CustomSerializer, DefaultFormats, Formats} import org.json4s.jackson.Serialization import scala.concurrent.ExecutionContext.Implicits.global import scala.concurrent.Future trait Da