简介
我的第一个Play应用是一个REST服务.这是一些我在编写API时学到的东西.Play用于编写JSON接口,Slick用于和数据库交互,其中PostgreSQL作为主数据库,Redis用于用户系统/统计数据和缓存,AWS S3用于存储文件,比如图片和视频音频.
项目结构
app
-- controllers
-- controllers go here
-- helpers
-- helper classes
-- models
-- Tables.scala // this contains all the table definitions
-- UserModel.scala
-- views
-- I dont use this directory as its a JSON API
-- Global.scala // this contains code that starts the akka actor system that Redis library uses
conf
-- application.conf
-- routes
数据结构
在app/models/Tables.scala中定义基本的数据库表结构,由于使用Slick和PostgreSQL进行交互,需要定义数据结构对PostgreSQL中的表结构进行映射.
Tables.scala有两个顶层对象,包含了和postgres表一一对应的case类的Types(Slick用于类型检查),和包含所有表结构定义的Tables.
一共有四个表:User, UserAuth, Message, UserConnection,下面是Types的定义:
import org.joda.time._ // this gives me DateTime
object Types {
case class User(id:Option[Int]=None, name: String, gender: Option[Int]=None, dob: Option[DateTime]=None, profilePic: Option[String]=None, createdAt: DateTime, status: Int)
case class UserAuth(userId: Int, `type`: Int, key: String, secret: String) // type is a keyword in scala so to tell scala compiler to not interpret it the keyword type, i sorround it by backticks
case class UserConnection(fromUserId: Int, toUserId: Int, connectedAt: DateTime) // directed graph, used for follower/following relationship
case class Message(id:Option[Int]=None, fromUserId: Int, toUserId: Int, sentAt: DateTime, text: Option[String]=None)
}
上面的每个case类中,id字段的类型均为Option[Int],并且给出默认值为None,因为在往数据库插入一行数据时并不能直到这行数据的id值,因此当插入时并不需要提供该字段的值,而是由postgres自动提供.
case类User中gender的类型为Option[Int],同时提供默认值为None,因为用户可能并不会提供性别,因此在数据表中定义为null,字段dob和profilePic也是一样道理.
下面是数据表定义:
import scala.slick.ast.ColumnOption.DBType // need this to use data types like varchar and text
import scala.slick.driver.PostgresDriver.simple._ // need to interact with postgres
import com.github.tototoshi.slick.PostgresJodaSupport._ // need for use postgres's datetime types
object Tables {
class User(tag: Tag) extends Table[Types.User](tag, "users") {
def id = column[Int]("id", O.PrimaryKey, O.AutoInc)
def name = column[String]("name")
def gender = column[Option[Int]]("gender")
def dob = column[Option[DateTime]]("dob")
def profilePic = column[Option[String]]("profile_pic")
def createdAt = column[DateTime]("created_at")
def status = column[Int]("status", O.Default(1)) // status has defaul vlaue of 1
def * = (id.?,name,gender,dob,profilePic,createdAt,status) <> (Types.User.tupled, Types.User.unapply)
}
class UserAuth(tag: Tag) extends Table[Types.UserAuth](tag, "user_auth") {
def userId = column[Int]("user_id")
def `type` = column[Int]("type")
def key = column[String]("key")
def secret = column[String]("secret")
def * = (userId,`type`,key,secret) <> (Types.UserAuth.tupled, Types.UserAuth.unapply)
def user = foreignKey("USER_FK", userId, TableQuery[User])(_.id)
def idx = index("USER_AUTH", (userId, `type`), unique = true)
}
class UserConnection(tag: Tag) extends Table[Types.UserConnection](tag, "user_connections") {
def fromUserId = column[Int]("from_user_id")
def toUserId = column[Int]("to_user_id")
def connectedAt = column[DateTime]("connected_at")
def * = (fromUserId,toUserId,connectedAt) <> (Types.UserConnection.tupled, Types.UserConnection.unapply)
}
class Message(tag: Tag) extends Table[Types.Message](tag, "messages") {
def id = column[Int]("id", O.PrimaryKey, O.AutoInc)
def fromUserId = column[Int]("from_user_id")
def toUserId = column[Int]("to_user_id")
def sentAt = column[DateTime]("sent_at")
def text = column[String]("text", DBType("text"))
def * = (id.?,fromUserId,toUserId,sentAt,text) <> (Types.Message.tupled, Types.Message.unapply)
}
}
“class User(tag: Tag) extends Table[Types.User](tag, “users”)”告诉Scala下面的表结构定义对应于case类Types.User,同时表名为”users”.
“def id = column[Int](“id”, O.PrimaryKey, O.AutoInc)”表示字段”id”为该表的自增主键ID.
“def gender = column[Option[Int]](“gender”)”表示字段gender是Int类型,并且可能为空.
“def * = (id.?,name,gender,dob,profilePic,createdAt,status) <> (Types.User.tupled, Types.User.unapply)”告诉Slick,任何从该表查询到的行,都返回括号中的列,同时提供数据表到case类的交互转换(参考详解).
“def user = foreignKey(“USERFK”, userId, TableQuery[User])(.id)”,该方法定义了一个外键,即UserAuth表中的userId字段引用User表中的id字段.
由于每个用户对应不同类型的网站(FB,google,Twitter)都有一个唯一的认证,以此通过”def idx = index(“USER_AUTH”, (userId, `type`), unique = true)”方法创建一个符合唯一索引.
如果想要使用postgres中的字段类型时,比如varchar,则可以按这样的方式:”def text = column[String](“text”, DBType(“text”))”,使用DBType进行声明,甚至声明字段长度:”def text = column[String](“text”, DBType(“varchar(100)”))”.