你不能通过遵守规则来学会走路。你得从实践中学习,从失败中学习。——理查德·布兰森
Inspiration API是我尝试改善Scala技能的旅程的开始。 用这种语言编写的代码很少,所以我决定尝试使用该语言和Play Framework一起构建基本的API。 有时候我不需要采取教科书的方法,而只是偶尔动手做事并从错误中吸取教训。 我的意思是...这有多难?
直到最近,我的主要编码重点一直放在Frontend或更多JavaScript重服务上。 出于工作和个人目的,我想扩展自己的技能范围,来涵盖更广泛的编程实践,因此,我找到了通往Scala的途径。 我已经在大学学习过Java的OOP基础知识,并接受了一些Scala培训,但是对使用JVM感到生疏,并且对功能编程范式的更复杂用法视而不见,因此我不准备采用它。
灵感API
代码库:在这里可用!
我猜想API的名称是不言而喻的->我需要一些项目来使我保持灵感并对学习一种新语言感兴趣。 你们中许多人都知道,在一整天的工作后,最好的时候很难找到能量编码!
猜这个API的名字是不言而喻的,我需要一些项目来保持我学习一门新语言的灵感和兴趣。你们很多人都知道,即使在最好的情况下,经过一整天的工作,也很难找到能量编码!
请注意,我在这里说的数据是我制作API的首次尝试,它的重点是设置返回响应的基本GET端点。我不是依赖于以JSON对象的基本数组开始的DB,而是在另一个项目中使用了类似的技巧来在静态站点上填充下拉菜单,而不是依赖于DB。从小处着手,然后向上攀登……
我设置了一系列github问题,这些问题与整个RESTful API里程碑相关联,基本上每个CRUD功能大约有1个问题:
刚开始时,我从播放网站克隆了一个Play / Scala种子。回想起来,它具有很多我会嬉戏地称为Bash bloatware的功能,这对于仅使用SBT在本地构建项目不是必需的-但是它确实做到了 工作!
使用play的主要注意事项是,它应该像修改路由配置一样简单,确定指向特定控制器的GET、POST、PUT、DELETE端点。
在Scala中处理JSON
立刻发现,在JS中搜索如何执行与在Scala中执行搜索之间所需的Google Fu水平存在很大差异。 公平地说,我可以接受JSON是JavaScript固有的...我们都可以同意Stack Overflow开发不是一种最佳的编程方式,但是它可以证明是一种有用的工具,当您开始并希望使用该工具进行构建时,您的使用范围非常有限!
因此,我的JSON解决方案(在此之前为数组实现)的基本逻辑是以Play控制器为特色的,该控制器看起来有点像这样(原谅占位符变量名...我从没在生产代码中这样做……老实!):
路线:
# Routes
# This file defines all application routes (Higher priority routes first)
# https://www.playframework.com/documentation/latest/ScalaRouting
# ~~~~
# An example controller showing a sample home page
GET / controllers.HomeController.index
# Map static resources from the /public folder to the /assets URL path
GET /assets/*file controllers.Assets.versioned(path="/public", file: Asset)
# Inspiration CRUD endpoints
GET /inspiration controllers.InspirationController.index
灵感控制器:
package controllers
import javax.inject._
import play.api._
import play.api.mvc._
import play.api.libs.json._
import scala.collection.mutable.ArrayBuffer
/**
* This controller creates an `Action` to handle HTTP requests to the
* application's home page.
*/
@Singleton
class InspirationController @Inject()(cc: ControllerComponents) extends AbstractController(cc) {
def index() = Action { implicit request: Request[AnyContent] =>
Ok(generateQuote(y, scala.util.Random.nextInt(10)))
}
// json method of generating quotes
// (y(scala.util.Random.nextInt(10))\"quote").get
var y: JsValue = Json.arr(
Json.obj("quote" -> "Make your life a masterpiece, imagine no limitations on what you can be, have or do.", "author" -> "Brian Tracy"),
Json.obj("quote" -> "We may encounter many defeats but we must not be defeated.", "author" -> "Maya Angelou"),
Json.obj("quote" -> "I am not a product of my circumstances. I am a product of my decisions.", "author" -> "Stephen Covey"),
Json.obj("quote" -> "We must let go of the life we have planned, so as to accept the one that is waiting for us.", "author" -> "Joseph Campbell"),
Json.obj("quote" -> "Believe you can and you're halfway there.", "author" -> "Theodore Roosevelt"),
Json.obj("quote" -> "We know what we are, but know not what we may be.", "author" -> "William Shakespeare"),
Json.obj("quote" -> "We can't help everyone, but everyone can help someone.", "author" -> "Ronald Reagan"),
Json.obj("quote" -> "When you have a dream, you've got to grab it an never let go.", "author" -> "Carol Burnett"),
Json.obj("quote" -> "Your present circumstances don't determine where you can go; they merely determine where you start.", "author" -> "Nido Quebein"),
Json.obj("quote" -> "Thinking: the talking of the soul with itself.", "author" -> "Plato")
)
// Function that returns a random string include quote & author
def generateQuote( quotes:JsValue, random:Int) : String = {
var quote:JsValue = (quotes(random)\"quote").get
var author:JsValue = (quotes(random)\"author").get
return (author.as[String] + ": " + quote.as[String])
}
// array method of generating quotes
// quotes(scala.util.Random.nextInt(10))
// var quotes = ArrayBuffer[String]()
// quotes += "Make your life a masterpiece, imagine no limitations on what you can be, have or do. - Brian Tracy"
// quotes += "We may encounter many defeats but we must not be defeated. - Maya Angelou"
// quotes += "I am not a product of my circumstances. I am a product of my decisions. - Stephen Covey"
// quotes += "We must let go of the life we have planned, so as to accept the one that is waiting for us. - Joseph Campbell"
// quotes += "Believe you can and you're halfway there. - Theodore Roosevelt"
// quotes += "We know what we are, but know not what we may be. - William Shakespeare"
// quotes += "We can't help everyone, but everyone can help someone. - Ronald Reagan"
// quotes += "When you have a dream, you've got to grab it an never let go. - Carol Burnett"
// quotes += "Your present circumstances don't determine where you can go; they merely determine where you start. - Nido Quebein"
// quotes += "Thinking: the talking of the soul with itself. - Plato"
}
哇,就像这样,我们有我们的GET /灵感起来和运行!
数据库连接
由于多个原因,这是该项目中最痛苦的部分。
您是否知道您无法在Windows 10 Home Edition上本地运行Docker? 您必须结合使用Docker Toolbox和无业游民或某些virtualbox设置吗?
我想使用Docker运行我的服务将连接到的简单postgres数据库。 我以前使用过Docker,并认为这将是最简单的解决方案! 这不应该引起我那么多问题(和时间),但是确实如此。 我通常在工作中的MacBook上编写代码,但在家里的Windows PC上编写个人项目代码-我发现这是在各个平台上保持灵活性的好方法。 最后,我称之为退出并使用可信赖的MacBook解决了问题(一旦我在本地克隆Dockerfile,该MacBook几乎立即起作用)。
为了使事物保持独立,我在存储库中包含一个基本的dbsetup.sql文件,用户可以使用一个简单的命令将其加载到Docker容器中。
dbsetup.sql(如果您有更好的默认引号,请在下面添加注释!):
\c inspiration_db
CREATE TABLE quotations(
index serial,
author varchar(255) NOT NULL,
quote varchar(1000) NOT NULL
);
INSERT INTO quotations (author, quote) VALUES ('Brian Tracy', 'Make your life a masterpiece, imagine no limitations on what you can be, have or do.');
INSERT INTO quotations (author, quote) VALUES ('Maya Angelou', 'We may encounter many defeats but we must not be defeated.');
INSERT INTO quotations (author, quote) VALUES ('Stephen Covey', 'I am not a product of my circumstances. I am a product of my decisions.');
INSERT INTO quotations (author, quote) VALUES ('Joseph Campbell', 'We must let go of the life we have planned, so as to accept the one that is waiting for us.');
INSERT INTO quotations (author, quote) VALUES ('Theodore Roosevelt', 'Believe you can and you''re halfway there.');
INSERT INTO quotations (author, quote) VALUES ('William Shakespeare', 'We know what we are, but know not what we may be.');
INSERT INTO quotations (author, quote) VALUES ('Ronald Reagan', 'We can''t help everyone, but everyone can help someone.');
INSERT INTO quotations (author, quote) VALUES ('Carol Burnett', 'When you have a dream, you''ve got to grab it an never let go.');
INSERT INTO quotations (author, quote) VALUES ('Nido Quebein', 'Your present circumstances don''t determine where you can go; they merely determine where you start.');
INSERT INTO quotations (author, quote) VALUES ('Plato', 'Thinking: the talking of the soul with itself.');
用于设置Docker数据库和在上面的行中插入的Docker命令:
docker-compose up -d
psql -h localhost -U user inspiration_db -f dbsetup.sql
在这一点上,我不得不开始Google搜索:“如何使用scala连接到postgres db”,并且出现了很多不同的库和结果,例如jdbc,postgres-scala,doobie等。仅仅获得几行文档来实现一个超级简单的实现有点让人不知所措,而且很难。最后,我使用了一个名为Slick的库。
解决了Slick之后,我设置了一个基本类来表示数据库的报价条目。 在弄清楚如何处理串行(自动递增)postgres值之前,我不得不做一些修补工作,但这就是一个最好的故事! 只需享受完整的功能代码!
类匹配引号结构构成了postgres DB:
// Matches schema of the docker-compose psql DB quotations table
class Quotes(tag: Tag) extends Table[(Int, String, String)](tag, "quotations") {
def index = column[Int]("index")
def author = column[String]("author")
def quote = column[String]("quote")
def * = (index, author, quote)
}
此时,其余的“艰苦”工作归结为弄清楚如何将postgresSQL转换为这种Slick样式的语法。 以下是各种端点工作方式的粗略分类:
GET /灵感
import scala.slick.driver.PostgresDriver.simple._
def index() = Action { implicit request: Request[AnyContent] =>
Ok(generateQuote(scala.util.Random.nextInt(10)))
}
val connectionUrl = "jdbc:postgresql://localhost:5432/inspiration_db?user=user"
def generateQuote(random:Int): String = {
var output = ""
// connecting to postgres db for accessing data
Database.forURL(connectionUrl, driver = "org.postgresql.Driver") withSession {
implicit session =>
val quotes = TableQuery[Quotes]
// SELECT * FROM quotations WHERE id=randomInt
quotes.filter(_.index === random+1).list foreach { row =>
output = row._2 + ": " + row._3
}
}
output
}
开机自检/灵感
import scala.slick.driver.PostgresDriver.simple._
def add() = Action { request =>
val body: AnyContent = request.body
val json: Option[JsValue] = body.asJson
val author = json.get("author").toString.stripPrefix("\"").stripSuffix("\"").trim
val quote = json.get("quote").toString.stripPrefix("\"").stripSuffix("\"").trim
addQuote(author, quote)
Ok("Successfully updated quotations DB")
}
val connectionUrl = "jdbc:postgresql://localhost:5432/inspiration_db?user=user"
def addQuote(author:String, quote:String): Unit ={
var index = 0
Database.forURL(connectionUrl, driver = "org.postgresql.Driver") withSession {
implicit session =>
val quotes = TableQuery[Quotes]
// getting id of last element in table
quotes.sortBy(_.index.desc).take(1).list foreach { row =>
index = row._1 + 1
}
quotes += (index, author, quote)
}
}
PUT /灵感
import scala.slick.driver.PostgresDriver.simple._
def replace() = Action { request =>
val body: AnyContent = request.body
val json: Option[JsValue] = body.asJson
val index: Int = json.get("index").toString.toInt
val author = json.get("author").toString.stripPrefix("\"").stripSuffix("\"").trim
val quote = json.get("quote").toString.stripPrefix("\"").stripSuffix("\"").trim
updateQuote(index, author, quote)
Ok("Successfully updated quotations DB")
}
val connectionUrl = "jdbc:postgresql://localhost:5432/inspiration_db?user=user"
def updateQuote(index:Int, author:String, quote:String) = {
Database.forURL(connectionUrl, driver = "org.postgresql.Driver") withSession {
implicit session =>
val quotes = TableQuery[Quotes]
quotes.filter(_.index === index).update(index, author, quote)
}
}
删除/灵感/:索引
import scala.slick.driver.PostgresDriver.simple._
def delete(index: Int) = Action { request =>
deleteQuote(index)
Ok(s"Successfully deleted entry $index")
}
val connectionUrl = "jdbc:postgresql://localhost:5432/inspiration_db?user=user"
def deleteQuote(index:Int): Unit = {
Database.forURL(connectionUrl, driver = "org.postgresql.Driver") withSession {
implicit session =>
val quotes = TableQuery[Quotes]
quotes.filter(_.index === index).delete
}
}
路线:
GET /inspiration controllers.InspirationController.index
POST /inspiration controllers.InspirationController.add
PUT /inspiration controllers.InspirationController.replace
DELETE /inspiration/:index controllers.InspirationController.delete(index: Int)
有了它——Scala&Play中的基本RESTful API!
代码库:在这里可用!
经验教训
该项目是摆脱书本并尝试通过直接开发一些东西来学习的好方法。 我肯定从中学到了一些小痛点:
- 如何用Scala连接到Postgres DB
- 如何用scala处理数组和JSON数据结构
- 如何使用Play框架处理路线
- 如何在Play框架中设置路径的基本前端
- 处理Docker在Windows…这个问题还在困扰着我浪费的时间!
改进之处
随着时间的推移,我计划对该项目进行大量补充,其中包括:
- 调整GET端点以返回索引中大于10的数据库中的项目->当前这是硬编码的,但应该很容易将其换成数据库计数
- 为生成的API设置API浮动定义->这只是一些有用的附加经验
- 将该API部署到某个地方-> Heroku现在是最受欢迎的...在部署之后,实现一些端点跟踪和分析将很有趣->也可能包含一些oAuth,但这通常让人头疼。
- 使用Swagger Codegen之类的服务从生成的Swagger开发基本SDK。
- 添加测试
- 修改路线上的“确定”部分以返回正确的响应,即200、201、202等。
与往常一样,如果您有任何反馈,建议或想法,请随时在下面分享。
'直到下次!
原文链接: https://dev.to//dan_mcm_/the-inspiration-api-a-project-built-with-scala--play-framework-195d