Scala api request

Am trecut de la web api-uri din .Net in Scala si am o mica nelamurire despre Async, Await, Future :roll_eyes:

Folosesc Play Framework cu Slick si MySQL ca db.

Am gasit destul de multe variante prin care se poate face un insert in db (ca exemplu), dar nu reusesc sa o aleg pe cea mai buna. Stiu ca e bine ca functiile controllerului sa fie async (in documentatia Play am gasit ca toate functiile sunt predefinite async).

Incep cu cel mai simplu exemplu … stergerea unei linii din tabela Users.

Controller.scala

def delete(id: Int) = Action.async {
    Future {
      val res = usersRepository.delete(id)
      if (res == 1) {
        Ok(Json.toJson("true"))
      }
      else {
        InternalServerError("Internal server error")
      }
    }
  }

repository.scala :

def delete(id: Int) {
    //dbUsers.filter(_.id === id).delete
        Try(dbUsers.filter(_.id === id).delete) match {
          case Success(response) => db.run(response)
          case Failure(_) => println("An error occurred!")
        }
  }

In repository, unde tin interogarile, ar fi indicat sa folosesc Try sau Future? Daca folosesc Future, in controller ma obliga sa schimb utilizarea repositoriului in
Await.result(usersRepository.delete(id), Duration.Inf) / ce ar fi indicat sa returnez din repository dupa executare, un Future[String, Int, Obj] sau pur si simplu obiectul in sine, in conditia in care toate metodele controllerului vor fi async. Intre repo si controller poate aparea un serviciu (in functie de complexitatea aplicatiei), deci ar deveni si mai complicat.

Un alt lucru ce nu imi place, daca definesc actiunea async (Action.async), ma obliga sa folosesc Future { tot corpul functiei } sau la fiecare return sa folosesc Future.successful(Ok(Json.toJson(“tralalala”))) … atat de urata e scrierea in Scala? :unamused: in .Net era mult mai elegant :))

si functia create() din controller, care e “cea mai complexa” de pana acum :laughing:

def create = Action.async(parse.json) {
    implicit request => {
      Future {
        User.form.bindFromRequest.fold(
          formWithErrors => BadRequest(Json.toJson("Some form fields are not validated.")),
          success => {
            val userFromJson = Json.fromJson[User](request.body)
            userFromJson match {
              case JsSuccess(user: User, path: JsPath) => {
                val createdUser = Await.result(usersRepository.create(user), Duration.Inf)
                Created(Json.toJson(createdUser))
              }
              case error@JsError(_) => {
                println(error)
                InternalServerError(Json.toJson("Can not create user"))
              }
            }
          }
        )
      }
    }
  }

plus repo:

def create(user: User): Future[User] = {
    val insertQuery = dbUsers returning dbUsers.map(_.id) into ((x, id) => x.copy(id = id))
    val query = insertQuery += user
    db.run(query)
  }

as fi vrut cumva sa mut toata actiunea din controller intr-un serviciu, iar aici sa ramana doar apelul serviciului, incerc sa tin controllerul cat mai simplu, functiile lui sa apeleze un serviciu ce intoarce raspunsul pentru client.

accept critici, e primul CRUD facut in Scala :))))) :rofl:

2 Likes

Sa inteleg ca ai vrea ca partea de future sa stea in service si service-ul sa-ti raspunda cu un future cu rezultatul? de ce ai vrea asta? Faptul ca este async este specific controllerului si nu ar trebui sa aiba treaba cu serviciul.

Daca ai o logica mai complexa poti sa o muti in service asta e clar, dar sa nu ii dai json ci niste bean-uri care le populezi cumva, sa nu depinda de controllerul tau.

In create acolo, daca il muti in serviciu si ai un caz de eroare arunci o exceptie si o prinzi in controller si trimiti un mesaj de eroare.

1 Like

cineva imi spunea sa utilizez functii async (asta inteleg) si sa evit folosirea Await.result() (daca fac asta, va trebui sa modific in repo, sa nu-mi mai intoarca Future[User]), dar imi da eroare la return din controller Created(Json.toJson(createdUser)) No Json serializer found for type Unit. Try to implement an implicit Writes or Format for this type.

object User {
  implicit val jsonWrites = Json.writes[User]
  implicit val userReads = Json.reads[User]
}

Ideea e ca n-as vrea sa folosesc metode diferite pentru fiecare actiune (get, create, update, delete) doar pentru simplul fapt ca …asa merge. De ex, in repo.Update nu intorc un Future[] ca in celelalte, ci ma folosesc de Try

def update(user: User)  {
    Try(dbUsers.filter(_.id === user.id).update(user)) match {
      case Success(response) => {
        db.run(response)
        return user
      }
      case Failure(_) => println("An error occurred!")
    }
  }

iar in controller:

def update(id: Int) = Action.async(parse.json) {
    implicit request => {
      Future {
        User.form.bindFromRequest.fold(
          formWithErrors => BadRequest(Json.toJson("Some form fields are not validated.")),
          success => {
            val userJson = request.body
            val user = new User(
              (userJson \ "id").as[Int],
              (userJson \ "userName").as[String],
              (userJson \ "firstName").as[String],
              (userJson \ "lastName").as[String],
              (userJson \ "phone").as[String]
            )

            val updatedUser = usersRepository.update(user)
            Ok(Json.toJson(user))
          })
      }
    }
  }

Pe langa faptul ca nu inteleg de ce nu se aplica user.reads automat si trebuie sa scot toate campurile din json, aici nu folosesc Await.result() (deoarece nu intorc Future din repo) si incerc sa imi dau seama de ce metoda e mai bine de urmarit. Cea pentru functia create, sau cea pentru update ? :crazy_face:

merci

" No Json serializer found for type Unit. Try to implement an implicit Writes or Format for this type."
Ideea e ca slick e reactiv nu stiu ce, si el iti returneaza tot timpul Unit-uri care nu poti sa le serializezi asa usor si nici nu ar avea sens. Adica iti returneaza Futures ca sa fim mai clari, deci daca vrei sa obti valoarea trebuie sa faci await sau cumva sa obtii rezultate. De atfel ai vazut si tu pentru ca in repository returnezi in general futures direct din slick. Acum, depinde de exemplu ai putea sa nu returnezi niciodata future din repository si sa obtii acolo valorile reale si sa le returnezi, cred ca asa ar fi cel mai simplu in prima faza.

1 Like