Skip to content
No description, website, or topics provided.
Scala
Branch: master
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Type Name Latest commit message Commit time
Failed to load latest commit information.
.github/workflows
project
src
.gitignore
README.md Separate README with 'How to use' section Nov 11, 2019
build.sbt

README.md

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 += "com.github.fomkin" %% "korolev-garcon" % "0.1.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 frienListGarcon: 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 =>
    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.

You can’t perform that action at this time.