wk voetbalpool webapp: dag 4

 
07 april 2010

Vandaag wordt het eens tijd om naar Actoren te kijken. We hebben voor het eind van de week een milestone staan om een prototype actor up-and-running te hebben. Gelukkig heeft Scala in de standaardbibliotheek een package scala.actors waar al wat basisfunctionaliteit in zit, dus dat kunnen we goed gebruiken. Ondertussen is Anatoly druk in de weer met het front-end. Hij heeft nu een single-page benadering op basis van GWT in de grondverf. Misschien kan het een multipage-omgeving worden, maar daarvoor moet hij nog wat dieper in GWT duiken.

Inmiddels zijn we het er behoorlijk over eens dat een REST-interface het makkelijkst zal zijn om het domein mee aan te spreken, en GWT zou dat toch in elk geval wel aan moeten kunnen spreken. Maar het lijkt er redelijk op dat we in onze Lift-code generieke GET- en POST-requests aankunnen, dus het zal niet zo’n probleem zijn om voorkant en achterkant aan elkaar te knopen. Lift heeft als conventie dat je bij het opstarten van de webserver methode Boot.boot aanroept. Daarin kun je regelen dat requests met een bepaald pad door eigen code wordt afgevangen in plaats van dat het door het Lift-framework wordt geïnterpreteerd.

class Boot 
{
  def boot 
  {
    LiftRules.dispatch.prepend(
      UserApi.dispatch)
  }
}


object UserApi 
{
  def dispatch: LiftRules.DispatchPF = 
  {
    case r@Req(
      "Registreren" :: "put" :: Nil, 
      _, GetRequest) => 
      () => Full(registreer(
        r.param("name"), r.param("password")))

    case r@Req(
      "ChangePassword" :: "put" :: Nil, 
      _, GetRequest)  => 
      () => Full(changePassword(
        r.param("name"), r.param("password")))
  }
}

Bovenstaande code zorgt ervoor dat het GET-request naar /Registreren/put?name=Jan&password=secret afgehandeld wordt door de methode-aanroep registreer("Jan", "secret") om een nieuwe gebruiker te registreren, en soortgelijk /ChangePassword/put?name=Jan&password=geheim door de methode-aanroep changePassword("Jan", "geheim").

Nu we dat voor elkaar hebben, kunnen we een aantal actoren gaan bouwen om deze requests het domein in te schieten. De registreer-request moet ergens afgehandeld worden, maar waar? We hebben een Actor nodig om tegenaan te praten, maar het kenmerk van een nieuwe gebruiker is dat er nog geen Actor tegenoverstaat. In eerste instantie willen we het simpel oplossen door ter plekke een nieuwe UserActor aan te maken, maar dat is natuurlijk niet erg event-gedreven.

In plaats daarvan zou alles via een MessageBus moeten lopen. Dat ding moet zelf natuurlijk ook een Actor zijn, dus we hebben hier een mooie eerste gelegenheid om er een te schrijven. Het domein is op dit moment nog volledig onzichtbaar voor de buitenwereld, dus we zullen wat logging moeten doen. Het kost nog even wat moeite om een log4j-logger aan jetty te knopen, maar met wat googlen en een nieuwe web.xml lukt het uiteindelijk wel.

We definiëren eerst een aantal messages die in het domein rondgeslingerd gaan worden, en gebruiken er dan eentje van om mee te testen:

abstract class WKMessage

abstract class SystemMessage extends WKMessage

case class RegisterUserMessage(
     username: String, password: String
) extends SystemMessage

object MessageBus extends Actor {
  var actors: List[Actor] = List()
  def act() {
    loop {
      react {
        case RegisterUserMessage(username, password) => {
          log4j.debug("RegisterUserMessage picked up: " + username + ", " + password)
          var user = new UserActor(username, password)
          registerActor(user)
          user.start
          actors.foreach(a=>a ! new UserRegisteredMessage(username))
        }
      }
    }
  }

  def registerActor(a: Actor) {
    if (!actors.exists(actor=>(actor==a))) {
      actors = a::actors
    }
  }
}

De constructie loop{ react{ ... }} zorgt hier voor de herhaaldelijke afhandeling van binnenkomende berichten. Als het bericht van type RegisterUserMessage is, dan doen we wat logging (zodat we ook kunnen zien dat het bericht is binnengekomen), maken we een nieuwe UserActor aan, registreren we hem en daarna starten we hem, zodat hij inderdaad ook berichten gaat verwerken. Niet onbelangrijk: in eerste instantie waren we dat vergeten en hebben we nog een poos moeten zoeken hoe het toch kan dat berichten niet aankwamen…? Het blijkt dus dat sommige dingen in Scala/Lift wel per conventie gaan (wat erg lastig is) terwijl je andere dingen gewoon met het handje moet doen (wat ook erg lastig is) en je als onwetende nitwit niet weet wanneer wat het geval is (wat nog wel het lastigst is). Maar uiteindelijk weten we nog voor de (late) lunch ons eerste message te versturen.

Na de lunch gaat het helaas niet meer allemaal zo snel. We gooien nog wat nieuwe typen messages heen en weer, maar zodra ik probeer een Comet-elementje op een website te plaatsen, en met een ajax-button een message het domein in te schieten, gaat het mis. Ten eerste blijkt mijn JavaScript wat roestiger te zijn dan ik had gehoopt, en het debuggen ervan in Netbeans is nog niet zover als ik had gehoopt, zodat verkeerde attributen of methode-namen onopgemerkt blijven.

Daarnaast komen nu ook de beperkingen en eigenaardigheden van Lift aan het licht: op een gegeven moment heb ik mijn testpagina zover uitgekleed dat hij niet veel meer zou moeten doen dan mijn HelloWorld van van de week, maar zelfs dat doet hij niet. En dan duurt het even voordat je erachter bent dat de conventie is om in Boot.boot() te zeggen: LiftRules.addToPackages("x.y.z") maar dat je daadwerkelijke klasse dan in package x.y.z.snippet moet staan. Logisch! Verder krijg ik nu ineens overal OutOfMemoryExceptions, wat pas verholpen wordt zodra jetty opnieuw opgestart is, blijkt dat in je template verplicht tenminste één verwijzing naar een Scala-object moet zitten (een stacktrace in je browser is de straf), kan je niet zomaar scripts opnemen (ook hier een stacktrace) maar blijkt het weer geen probleem om niet-bestaande code (te proberen) aan te roepen.

Aan het eind van de dag ben ik dan toch tenminste zover dat ik door een druk op de knop een nieuwe gebruiker kan aanmaken in het domein. Dat is nu nog alleen te zien aan het feit dat jetty dan wat logging doet, maar morgen wil ik ook een CometActor aan de webpagina hangen die het UserRegisteredMessage opvangt en op het browserscherm tevoorschijn tovert. Ik heb alleen al wel gezien dat de voorbeeldcode op de website… uiteraard niet klopt. En er staat nog een uitdaging voor de deur: hoe knopen we dit uiteindelijk allemaal aan GWT? Maar daarover gaan we ons pas later druk maken.

En daarmee werd het half zes, en alles was wel.


Werken met ?
Kijk dan bij onze mogelijkheden voor zowel starters als ervaren engineers.


Categorieën: Development


Reactie

  • Dit wordt toch niet de laatste, hoop ik? Ideaal om op deze manier up to date te blijven. Anatoly en ik hadden al zoiets in gang voor zijn EDA werk tot nu toe, maar dat ging via de mail. Dit is helemaal mooi, de resultaten zijn direct gepubliceerd. Ik kan me wel voorstellen dat ze korter worden.. Tevens wil ik nog een keer melden dat je een leuke schrijfstijl hebt, leest prettig.

    Morgen ben ik er ook weer, ik verwacht gewoon weer aan te haken! Ik spring de laatste tijd vaak heen en weer tussen de CLR en de JVM, dus ga daar gewoon lekker mee door. :-)

    Geplaatst op 08 april 2010 om 20:20 Permalink