woensdag 31 maart 2010

Windows Azure

Afgelopen donderdag was ik bezoek bij Ordina in Nieuwegein waar DotNed een sessie organiseerde over Windows Azure ofwel het Azure Service Platform. Ik was benieuwd hoe Azure zich verhoudt ten op zichte van Google App Engine, een service van Google die hetzelfde probleem op probeert te lossen.

Welk probleem is dat dan? Schaalbare, betrouwbare hosting. Wanneer je een website hebt kun je deze hosten op je eigen PC of bij een hosting provider. Mocht je website naar verloop van tijd wat populairder worden, dan heb je al snel een probleem als je tegen de capaciteiten van die webserver aan loopt. De oplossing hiervoor is opschalen, wat wederom een hoop problemen met zich mee neemt. Er zijn op dit moment tenminste drie providers die dit opschaal probleem aanpakken: Google, Amazon en Microsoft.

Google kwam in april 2008 met zijn App Engine. Hiermee haalden zij het hele idee van hosting overhoop. Niet langer had je een (virtuele) machine tot je beschikking, maar je kon je website draaien in de cloud van Google. Bij het online zetten van je website, had je vervolgens geen flauw idee waar je website gebleven was, maar hij bleef ten aller tijden bereikbaar op .appspot.com. Als je website populair was, dan maakt de App Engine meerdere instanties aan. Als je website minder populair wordt, worden deze instanties weer afgesbroken. Toegang tot een relationele database is er helaas niet, maar BigTable (het opslag systeem van Google) is wel aanwezig. Het mooie van BigTable is dat je tegen de tabel praat alsof het een enkele tabel is, maar de tabel kan verspreid zijn over meerdere servers in de wereld. Met andere woorden, het opschaal probleem is hiermee helemaal opgelost. Natuurlijk zijn er ook nadelen. Er worden op het moment van schrijven slechts 2 talen ondersteund: Python en Java. Daarnaast is er geen SQL ondersteuning zijn er vele restricties waarmee rekening gehouden moet worden.

Amazon loste dit op een iets andere wijze op. Zij gaan toch weer een beetje terug naar oorspronkelijke idee. Je huurt bij Amazon virtual machines. Die VM's draaien dan welleswaar iun de cloud, maar daar merk je niet zoveel van. Met die virtual machine kun je doen wat je wilt. Als je meer capaciteit nodig hebt, kun je dynamisch (of elastisch zoals ze het zelf noemen) instanties aanmaken. Je betaald daarbij per uur.

Microsoft kwam afgelopen januari met hun eigen oplossing: Azure. Daarmee gaan ze een beetje tussen Amazon en Google inzitten. Enerzijds maken ze ook gebruik van virtual machines/instanties die een website/webapp (Web Role) of achtergrond proces (Worker Role) kunnen draaien. Ook hier moeten de instanties handmatig opgehoogd worden wanneer de capaciteit bereikt wordt. Je kunt echter met zo'n instanties niet zomaar alles doen. Je kunt geen software installeren, afgezien van de speciale Azure software die je met Visual Studio 2010 hebt kunnen maken. Daarnaast ben je afhankelijk van de services die Microsoft levert. Naast integratie met diverse Windows Live services, hebben ze ook een aantal nieuwe services in het leven geroepen om Azure bij te staan waaronder SQL Azure (een SQL Server versie die in de cloud draait), Azure Tables (Microsofts variant van BigTable), Queue Service (om taken van de Web Role naar de Server Role te sturen), Blob (om grote data structuren op te slaan) en een Filesystem (om simpelweg bestanden weg te schrijven). Uiteraard kun je wel gewoon gebruik maken van alle services die je zelf kunt hosten en die over het internet beschikbaar zijn.

Eindelijk begeeft Microsoft zich ook op de cloud markt, maar mijns inziens zijn ze toch nog iets te vroeg ingestapt. Er ontbreken een aantal features waarbij ik met name het automatisch opschalen van het aantal servers mis. Maar de ontwikkelaars kunnen vast beginnen en Microsoft kennende, zullen ze in de komende maanden of jaren hun Azure Service Platform blijven verbeteren.

woensdag 24 maart 2010

GWT en Maps

Een locatie kan informatie verrijken en met de opkomst van Google Maps en concurrenten wordt daar op internet veel gebruik van gemaakt. Bij menig online service kun je een event, foto of bericht koppelen aan een locatie. Dat is op zich ook niet zo gek als je ziet hoe makkelijk Google het ons gemaakt heeft. Er is een uitgebreide en goed gedocumenteerde API om Google Maps ook op jouw website een plekje te geven.

Afgelopen jaar probeerde ik voor een project Google Maps te integreren in een Java programma. Dat viel nog niet mee. Dus de vraag waar ik nu mee zit is of Google Maps te integreren is binnen GWT.

Het antwoord zal je niet verrassen, natuurlijk kan dat en het is nog makkelijker dan ik verwacht had. Ik ga hieronder niet uitleggen hoe het werkt, maar verwijs naar een tutorial van Google waar het erg helder uit staat gewerkt.

http://code.google.com/p/gwt-google-apis/wiki/MapsGettingStarted

En mijn eigen probeersel:
http://waalwijk.yall.nl:8080/GoogleMaps/

woensdag 17 maart 2010

GWT en Comet

Lang geleden, toen het world wide web uitgedacht werd, is er weinig rekening gehouden met de behoefte die de gebruikers later zouden krijgen. Er is een protocol bedacht (HTTP) waarmee een client, meestal een browser, een bestand op kan vragen. Een webserver antwoord dan door het bestand terug te sturen. Een aanvraag kan eventueel voorzien worden van extra informatie in de vorm van key-value pairs. Later is er nog een kleine aanpassing aan dit HTTP protocol geweest om het wat efficienter te laten werken wanneer er meerdere bestanden binnen gehaald moeten worden. Verdere aanpassingen zijn uitgebleven.

Met de opkomst van JavaScript in de browser, en daarmee AJAX, werd het mogelijk om informatie ook op een andere manier naar de browser te sturen. Niet langer moest keer op keer een volledige pagina worden gedownload, maar alleen de nieuwe informatie kon opgehaald worden in een asynchrone request. Wachttijden zijn hierdoor drastisch verlaagd. Het principe is echter niet veranderd. Nog steeds wordt er een bestand opgevraagd en ontvangen. Enkel de verwerking van dit bestand in de browser is gewijzigd.

Maar wat nu als je web applicatie afhankelijk is van server updates? Het is dan erg onhandig om de server periodiek te vragen of er misschien al een update is. Ten eerste zit er dan bijna altijd een vertraging in en ten tweede zullen er veel lege berichten op en neer gestuurd worden op de momenten dat er geen updates zijn. Helaas hebben we hier te maken met restricties in het HTTP protocol.

Zoals het altijd gaat in de software wereld, is er toch altijd wel weer iemand met een briljante ingeving die een loophole ontdekt in het protocol. Zo ook in dit geval. Wanneer je een webrequest doet, heb je vaak in no time het antwoord binnen. Maar zo was het niet altijd. Toen het internet nog wat trager was, deden berichten er wat langer over. Een browser moest dan maar wachten tot er een keer een antwoord kwam. Het is moeilijk vast te stellen of er nog niet lang genoeg is gewacht, of dat er iets mis is gegaan. Daarom is afgesproken dat er na een instelbare hoeveelheid tijd, vanuit gegaan mag worden dat er iets mis is gegaan. Er kan dan een nieuw request verstuurd worden. Dit principe is uitgebuit in een techniek die later PTTH, Reverse AJAX , of Comet (een ander populair Amerikaans schoonmaak middel naast Ajax) genoemd is.

Het idee is het volgende. Een client doet een request, maar hij krijgt geen antwoord. De server houdt de lijn open. Er kunnen twee dingen gebeuren. Er verschijnt een update op de server, welke opgestuurd kan worden naar de client. De openstaande lijn kan hiervoor worden gebruikt. Het tweede dat kan gebeuren is dat er geen update beschikbaar is voordat de timeout op gaat treden. In dat geval wordt er een leeg antwoord gestuurd. De client doet vervolgens een nieuwe aanvraag waardoor het proces opnieuw begint. De twee nadelen die aanwezig waren bij het polling mechanisme, worden hier verkleind. Updates worden meteen naar de client gestuurd en het aantal lege berichten is minimaal. Comet is niet nieuw, maar bestaat al een aantal jaren, al is het niet bij iedereen even bekend.

Kunnen we deze techniek nu ook gebruiken binnen GWT? Natuurlijk. En het mooie is, het vervelende werk is al gedaan door andere mensen. Ik heb gezocht naar libraries en na enkele uren lezen en spelen, ben ik blijven steken bij een project genaamd GWTEventService. Van alles dat ik geprobeerd heb, werkte deze toch het lekkerst. Een client kan zich inschrijven op een event type, en de server kan een event van dit type publiceren. Er gaat dan bij de client, mits hij geabonneerd is op dit event-type, vrijwel meteen een event af.

Tijd voor een voorbeeld. Stel we hebben twee simpele chat Widgets (toch weer een chat voorbeeld) die volledig onafhankelijk van elkaar zijn. Beide kunnen ze een berichtje sturen naar de server en dit berichtje moet dan meteen doorgestuurd worden naar de andere chat Widgets.

Maak een nieuw project aan, download de jar-file van GWTEventService en voeg deze toe aan je build-path. Voeg ook de volgende regel toe in het .gwt.xml configuratiebestand.
<inherits name="de.novanic.eventservice.GWTEventService" />

De volgende stap is het implementeren van het event dat we willen versturen.
import de.novanic.eventservice.client.event.Event;
import de.novanic.eventservice.client.event.domain.Domain;
import de.novanic.eventservice.client.event.domain.DomainFactory;

public class MyEvent implements Event
{
   /**
    * This distinguishes the event from others
    */
   public static final Domain NS = DomainFactory.getDomain("server_message_domain");

   private String message;

   private String sender;

   /**
    * Needed for serialization
    */
   public MyEvent() {}

   public MyEvent(String sender, String message) {
       this.sender = sender;
       this.message = message;
   }

   public String getMessage() {
       return message;
   }

   public String getSender() {
       return sender;
   }
}

De volgende stap is het schrijven van de standaard RPC interface zoals dat binnen GWT gebruikelijk is. We definieren een methode waarmee een chat client een berichtje kan sturen.

import com.google.gwt.user.client.rpc.RemoteService;
import com.google.gwt.user.client.rpc.RemoteServiceRelativePath;

@RemoteServiceRelativePath("MyInterface")
public interface MyInterface extends RemoteService {
 public void say(String who, String what);
}

En zoals gewoonlijk hebben we ook de asynchrone variant.
import com.google.gwt.user.client.rpc.AsyncCallback;

public interface MyInterfaceAsync {
 public void say(String who, String what, AsyncCallback anAsyncCallback);
}

Deze methodes moeten we implementeren aan de server kant.

import nl.yall.test.eventservice.client.MyEvent;
import nl.yall.test.eventservice.client.MyInterface;
import de.novanic.eventservice.service.RemoteEventServiceServlet;

public class MyInterfaceImpl extends RemoteEventServiceServlet implements MyInterface {
   public void say(String who, String what) {
       MyEvent event = new MyEvent(who, what);
       addEvent(MyEvent.NS, event);
   }
}

Vervolgens moeten we enkel de client nog implementeren. Om het een beetje overzichtelijk te houden, heb ik alleen de essentiele dingen achter gelaten.

import de.novanic.eventservice.client.event.Event;
import de.novanic.eventservice.client.event.RemoteEventService;
import de.novanic.eventservice.client.event.RemoteEventServiceFactory;
import de.novanic.eventservice.client.event.listener.RemoteEventListener;

public class EventService implements EntryPoint {
 private final MyInterfaceAsync service =
  GWT.create(MyInterface.class);

 private RemoteEventService remoteService =
  RemoteEventServiceFactory.getInstance().getRemoteEventService();

 public void onModuleLoad() {
  HorizontalPanel panel = new HorizontalPanel();
  panel.add(new MyPanel("Gerben"));
  panel.add(new MyPanel("David"));

  RootLayoutPanel.get().add(panel);
 }

 private class MyPanel extends FlowPanel {
  private final TextBox messageBox = new TextBox();
  private final ListBox listBox = new ListBox(true);
  private final String user;
  
  public MyPanel(final String user) {
   this.user = user;
   
   setSize("60%", "60%");
   
   remoteService.addListener(MyEvent.NS, new RemoteEventListener() {
    public void apply(Event e) {
     if(e instanceof MyEvent) {
      MyEvent me = (MyEvent) e;
      listBox.addItem(me.getSender() + ": " + me.getMessage());
     }
    }
   });

   messageBox.addKeyPressHandler(new KeyPressHandler() {
    @Override
    public void onKeyPress(KeyPressEvent event) {
     if (event.getCharCode() == '\r') {
      sendMessage();
      messageBox.setText("");
     }
    }
   });
   
   listBox.setSize("100%", "80%");
   listBox.addItem("Listening...");
   add(new Label(user));
   add(listBox);
   add(messageBox);
  }

  private void sendMessage() {
   service.say(user, messageBox.getText(), new AsyncCallback() {
    public void onFailure(Throwable aThrowable) {}
    public void onSuccess(Void aResult) {}
   });
  }
 }
}

Tenslotte moeten we de servlet nog toevoegen aan de configuratie file voor de applicatie server (Web.xml)

<servlet>
  <servlet-name>EventService</servlet-name>
  <servlet-class>de.novanic.eventservice.service.EventServiceImpl</servlet-class>
</servlet>

<servlet-mapping>
  <servlet-name>EventService</servlet-name>
  <url-pattern>/eventservice/gwteventservice</url-pattern>
</servlet-mapping>

<servlet>
  <servlet-name>EventServlet</servlet-name>
  <servlet-class>nl.yall.test.eventservice.server.MyInterfaceImpl</servlet-class>
</servlet>

<servlet-mapping>
  <servlet-name>EventServlet</servlet-name>
  <url-pattern>/eventservice/MyInterface</url-pattern>
</servlet-mapping>

Het gebrek aan de mogelijkheid tot pushen van gegevens wordt vaak als een nadeel gezien van web applicaties, maar met de techniek die hierboven beschreven staat, hoeft dat dus niet zo te zijn.

Succes met spelen.

Gebruikte libraries:
http://code.google.com/p/gwteventservice/

Het voorbeeld:
http://waalwijk.yall.nl:8080/EventService/