Skip to content
This repository has been archived by the owner. It is now read-only.

fomkin/korolev-garcon

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

13 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Garcon

This extension intended to help you load data to a state in Korolev applications. It's especially useful when you have some data that loads in multiple parts of the app in a same way. Garcon allows to generalize data loading and remove this work from event handlers.

Lets see how it works. For example you have an app which manage user's friends:

case class User(deviceId: String, email: String)
case class ApplicationState(user: User, friends: List[User])

To make this app works, you should configure StateLoader.forDeviceId, which will load user and his friends from database.

This obvious solution may lead to problems with user experience. What if DBMS on heavy load? What if request is not optimized well? What if we work with real word case where we need to make a hundreds of SQL-queries to show user a first screen? Until data is not loaded, user looks on a blank page. This is unacceptable.

How to use

Garcon available in Maven Central. Garcon supports Scala 2.12 and 2.13 and Korolev 0.14.0 or higher. Add dependency to your project.

libraryDependencies += "org.fomkin" %% "korolev-garcon" % "0.3.0"

This library offers Demand data type, Garcon type class, and extension for Korolev which allows to manage data asynchronously.

Let's come back to friends list. First let's replace data with demands.

import korolev.garcon._

case class User(deviceId: String, email: String)
case class FriendList(xs: List[User])
case class ApplicationState(
  user: Demand[String, User, String],
  friends: Demand[String, FriendList, String]
)

Next we should define a way to load data (pseudo-SQL).

implicit val userGarcon: Garcon[Future, String, User, Throwable] =
  (id, modify) => sql"select * from user where id = $id"
   .one[User]
   .run
   .flatMap(user => modify(Demand.Ready(user)))
   .recover(e => modify(Demand.Error(e)))

implicit val friendListGarcon: Garcon[Future, String, FriendList, Throwable] =
  (id, modify) => sql"select from friends left join user on friends.of = user.id where friends.of = $id"
   .list[User]
   .run
   .flatMap(users => modify(Demand.Ready(users)))
   .recover(e => modify(Demand.Error(e)))

Finally we add extension to Korolev's config. Make sure that implicit garcons are in the scope.

KorolevServiceConfig[Future, ApplicationState, Any](
  extensions = List(
    Garcon.extension[Future, ApplicationState, Throwable],
    ...
  ),
  ...
)

Define StateLoader and renderer.

KorolevServiceConfig[Future, ApplicationState, Any](
  ...
  stateLoader = StateLoader.forDeviceId { deviceId =>
    Future.successful(
      ApplicationState(
        user = Demand.Start(deviceId, prev = None),
        friends = Demand.Start(deviceId, prev = None)  
      )
    )
  },
  render = { state =>
    Html(
      body(
        state.user match {
          case Demand.Ready(user) => div(s"User: ${user.email}")
          case _ => div("Loading user")
        },
        state.friends match {
          case Demand.Ready(FriendList(xs)) => ul(xs.map(x => li(x.email)))
          case _ => div("Loading friend list")
        }
      )
    )
  }
)

That's all. The extension will watch a state. When one of demands became Start it will run corresponding Garcon and reset demands to Eventually. After loading is complete demand will become Ready.

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Packages

No packages published

Languages