Вы не научитесь ходить, следуя правилам. Вы учитесь, делая и падая. – Ричард Брэнсон
API Inspiration – это начало моего путешествия по пути совершенствования моих навыков в Scala. Закодировав очень мало на этом языке, я решил попытаться создать базовый API, используя этот язык вместе с Play Framework. Вместо того, чтобы следовать подходу учебника, я часто нахожу, что иногда вам просто нужно испачкать руки и учиться на своих ошибках. Я имею в виду… насколько это может быть сложно?
До недавнего времени мое основное внимание при написании кода было сосредоточено на интерфейсе или более сложных сервисах JavaScript. Для работы и личных целей я хотел расширить свой набор навыков, чтобы охватить более широкий спектр методов программирования, и поэтому я нашел свой путь в Scala. Я изучал основы ООП Java в колледже и прошел некоторое обучение Scala на работе, но, будучи новичком в использовании JVM и слепым к более сложным методам использования парадигмы функционального программирования, я был плохо подготовлен к ее принятию.
API вдохновения
Кодовая База: Доступна Здесь!
Я предполагаю, что название API самоочевидно -> Мне нужен был какой-то проект, который вдохновлял бы меня и интересовал изучением нового языка. Как многие из вас знают, в лучшие времена бывает трудно найти энергетическое кодирование после целого дня работы!
Так что в этот момент я был немного ленив… Я в основном погуглил “вдохновляющие цитаты” и выбрал первые 10, с которыми я столкнулся, которые не звучали совсем глупо или нелепо, и решил использовать их для основы данных API.
Обратите внимание, что я говорю здесь о данных, поскольку моя первая попытка создания API была сосредоточена на настройке базовой конечной точки GET, которая возвращает ответ. Вместо того, чтобы полагаться на базу данных, я начал с базового массива объектов JSON, я использовал аналогичный трюк в другом проекте для заполнения выпадающего меню на статическом сайте вместо того, чтобы полагаться на базу данных. Начни с малого и проложи себе путь наверх, как я понял…
Я создал кучу проблем с github, которые были связаны с общей вехой RESTful API , по сути, это была более или менее 1 проблема для каждой функции CRUD:
Когда я начинал, я клонировал семя Play/Scala с веб-сайта play -> в ретроспективе это содержало довольно много того, что я игриво назову вредоносным ПО Bash, которое не было необходимо для простого создания проекта локально с помощью SBT – однако оно выполнило свою работу!
Основными соображениями при использовании play было то, что это должно быть так же просто, как изменение конфигурации вашего маршрутизатора, чтобы определить ваши конечные точки GET, POST, PUT, DELETE, указывающие на определенный контроллер.
Обработка JSON в Scala
Сразу же стало очевидно, что существует большая разница в уровне знаний Google, необходимых для поиска того, как что-то сделать в JS, по сравнению с тем, как что-то сделать в Scala. И, честно говоря, я могу согласиться с тем, что JSON является родным для JavaScript… Мы все можем согласиться с тем, что разработка с переполнением стека не является оптимальным способом программирования, однако она может оказаться полезным инструментом, когда вы начинаете и хотите создать что-то с помощью инструмента, с которым у вас было очень ограниченное использование!
Итак, основная логика моего решения JSON (а до этого – реализация массива) включала контроллер воспроизведения, который выглядел примерно так (простите за имена переменных-заполнителей… Я никогда не делаю этого в производственном коде … честно!):
Маршруты:
# 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"
}
Вау – просто так у нас есть наше вдохновение/вдохновение и работает!
Подключение к Базе Данных
Это была САМАЯ болезненная часть проекта по нескольким причинам.
Знаете ли вы, что вы не можете запустить Docker изначально в Windows 10 Home edition? Вы должны использовать Docker Toolbox в сочетании с vagrant или какой-либо установкой virtualbox?
Я хотел использовать Docker для запуска простой базы данных postgres, к которой будет подключаться моя служба. Я уже использовал Docker раньше и решил, что это будет самым простым решением! Это не должно было вызвать у меня так много проблем (и времени), но это произошло. Обычно я пишу на MacBook на работе, но дома на ПК с Windows для личных проектов – я нахожу, что это хороший способ оставаться на разных платформах. В конце концов я прекратил работу и решил использовать свой надежный MacBook (который сработал почти мгновенно, как только я клонировал Dockerfile локально).
Чтобы попытаться сохранить автономность, я включил в репозиторий базовый файл db setup.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: “как подключиться к базе данных postgres с помощью scala”, и там появилось множество различных библиотек и результатов, таких как jdbc, postgres-scala, doobie и многие другие… Было немного сложно и сложно просто получить несколько строк документации для супер простой реализации. В конце концов я выбрал библиотеку под названием Slick .
Остановившись на Slick, я настроил базовый класс для представления записей котировок для базы данных. Мне пришлось немного повозиться, прежде чем понять, как, черт возьми, обрабатывать последовательные (автоматически увеличивающиеся) значения postgres но об этой истории лучше не упоминать! Просто наслаждайтесь завершенным функционирующим кодом!
Структура кавычек, соответствующих классу, из базы данных postgres:
// 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 в этот синтаксис гладкого стиля. Вот приблизительная разбивка того, как работали различные конечные точки:
ПОЛУЧИТЬ/вдохновение
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)
}
}
ВКЛАД/вдохновение
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)
И вот оно у вас есть – базовый RESTful API в Scala & Play!
Кодовая База: Доступна Здесь!
полученные знания
Проект был хорошим способом уйти от книги и попробовать учиться, разрабатывая что-то напрямую. Были некоторые незначительные болевые точки, из которых я определенно извлек уроки, в том числе:
- Как подключиться к базе данных Postgres с помощью scala
- Как обрабатывать структуры данных Array и JSON с помощью scala
- Как обрабатывать маршруты с помощью Play Framework
- Как настроить базовый интерфейс для путей в Play Framework
- Обработка Docker в Windows … это все еще раздражает меня из-за потраченного впустую времени!
Улучшения
Есть куча дополнений, которые я планирую внести в этот проект с течением времени, в том числе:
- Настройка конечной точки GET для возврата элементов в БД с индексом выше 10 -> в настоящее время это жестко запрограммировано, но должно быть легко заменить его на количество БД
- Настройте определение API swagger для сгенерированного API -> это было бы просто полезным дополнительным опытом
- Разверните API где-нибудь -> Heroku на данный момент является ведущим фаворитом… После развертывания было бы интересно реализовать отслеживание и аналитику некоторых конечных точек -> потенциально также некоторый OAuth, но обычно это вызывает головную боль при настройке.
- Разрабатывайте базовые SDK на основе сгенерированного Swagger с помощью такого сервиса, как Swagger Codegen
- Добавление Тестов
- Измените раздел OK на маршрутах, чтобы вернуть правильный ответ, например 200, 201, 202 и т.д.
Как всегда, если у вас есть какие-либо отзывы, предложения или мысли, не стесняйтесь делиться ими ниже.
– До Следующего Раза!
Оригинал: “https://dev.to/dan_mcm_/the-inspiration-api-a-project-built-with-scala–play-framework-195d”