Wednesday 6 July 2016

Reactive Programming Part 2: Building a reactive auction site

In the previous post, we were introduced to the basic concepts of reactive programming. In this part, we will cover prototyping a real time auction application using some of the reactive programming concepts and the toolset listed above. The application will support:
  • Setup an auction with basic attributes such as title, initial price, and the product id
  • The live auctions be viewed and an user can bid for the item if the user is logged in.
but will not support: client side validation on forms, reporting highest bid, security rules.

We are going to use Google Firebase (https://firebase.google.com)  as our backend.  The Firebase Real-time Database is a cloud-hosted database where Data is stored as JSON and synchronised in real-time to every connected client. Although the technology is still evolving, it is a fantastic tool for prototyping reactive behaviour and building highly collaborative event driven applications.

The data model:

First let us define a model for our auction data. It will be a JSON list of auction objects. However, instead of nesting bids within the auction object, we will have a separate node of bids with child nodes as auction object. This relatively flat structure makes it easy and fast to search and index objects later on.

{   
  "auctions" : {   
   "auctionkey1" : {   
   "prodId" : "2",   
   "startingBid" : 1,   
   "title" : "auction 1"   
   },   
   "auctionkey2" : {   
   "prodId" : "2",   
   "startingBid" : 1,   
   "title" : "auction 2"   
   }  
  },   
  "bids" : {   
   "auctionkey1" : {   
   "bidkey1" : {   
    "price" : "10.01",   
    "userid" : "user1"   
   },   
   "bidkey2" : {   
    "price" : "10.02",   
    "userid" : "user2"   
   }  
   }   
  }   
}   

Landing Page and Navigation

Next we setup the landing page (app.component.ts) which is a simple nav bar with a content placeholder in which the single page application will load content based on the route configuration in app.routes.ts. The template for the landing page is shown below with navigations that are defined in app.routes.ts.


app.component.html
The template for the landing page page is shown below. It uses navigation routes that are defined in app.routes.ts.


app.routes.ts
The default route will load the AuctionsComponent that will display the list of auctions. Actions are setup in the AuctionsFormComponent.


app.component.ts
For authentication, we will use firebasese authentication provider that supports various social network authentications in addition to email/password authentication. For the purpose of this project, I have implemented only Google authentication. Also, the firebase provides a defaultFirebaseConfig object that can be bootstrapped into the AppComponent via main.ts.

Setting up a new auction


auction-form.component.html
The auction form template is setup as a model driven form template.

auction-form.component.ts
The form's DOM elements are bound to the view model via form controls. Controls can be seen as proxy objects to the DOM elements. A Control can be bound to an input element, and takes 3 arguments (all optional); a default value, a validator and a asynchronous validator. Controls can be grouped together within a controlgroup. The state of the form is available  through the control objects within the control group but in this case we are also using the [(ngModel)] binding to bind the form to a model object in AuctionFormComponent. This is not advisable but as the duality of state access may cause some confusion but some may prefer the less verbose approach of the ngModel.



auction.service.ts
The AuctionFormComponent uses a service component to create an auction node in the firebase server. The angularfire reference object gets injected into the service component by angular within the constructor of the service. The getProducts() call returns a dummy list of product. Alternatively, instead of createAuction, you could bind to an observable array representing the auctions node and push directly new auction data onto the Observable array using the 3-way binding of the Angular Fire2 framework. This concept is used in the auction listing and bidding user story as we will see in the next session.

Listing auction items and bidding

Before we get into the implementation of auction lists, we should familiarize ourselves with a couple of key concepts related to using firebase as a real-time datastore.

3-way binding

2-way binding (model to view synchronisation) is a well-known concept.  However, angularfire offers 3-way synchronisation which is the view to model to database synchronisation. Push changes on to the synchronised object and now any changes in the DOM are pushed to Angular, and then automatically to our database. And inversely, any changes on the server get pushed into Angular and straight to the DOM.

Concurrent writes

If a user adds a new bid it could be stored as /bids/auction/2. This would work if only a single user were adding auctions or bids, but in our real time auctions application many users may bid at the same time. If two bid simultaneously, then one of the bids would be deleted by the other. To handle this, Firebase provides a push() function that generates a unique key every time a new child is added. By using unique keys names for each new element in the list, several clients can add children to the same location at the same time without worrying about write conflicts. The unique ID generated by push is based on a timestamp, so list items will automatically be ordered chronologically.



We can see these concepts in play in the auctions listing and bidding component.

auctions.component.html
The auction listing template has ngFor loop that iterates over the auctions observables array. The async pipe unwraps the each item in the auctions observable as they arrive. The product images are stock images picked from the lorempixel images site indexed to the product id within the auction object. Any selected auction is highlighted and the selectedAuction property is set to the currently selected item in the list and you can then bid on the item.



auction.component.ts
The AuctionsComponent maintains a list of auctions as an Observable of auction items which get inistialised through the auction service. You can then select the item  and make bids using pushes onto auctions model object. The implementation of the getAuctions() method is interesting - it returns an observable that mimics the JSON data structure we saw earlier. When we select an auction and place bids, we can access the bids arrays using selectedAuction.bids and push new bids onto the observable array of bids with the selected auction as the key. In an implementation where the bids are in a separate component, you would have a getBids(key) method in the the service to access the bids node directly and then push bid objects using the same auction key. We can see the auction selection and bidding actions in the animated gif below.



Selecting and bidding

Once you have selected an item and logged in, we can bid for the item. This is implemented as a template driven form. Submitting the form, pushes a JSON object onto the bids node using the selected auction as a key.




What's missing

Besides the obvious lack of form validations and a more elaborate auction data model, the  application is missing some functionality such as ensuring that bids can be placed only if they are higher than the current highest bid. This could be implemented on the client side but any robust implementation would ensure that the server maintains ultimate control of who is the highest bidder. Currently,Firebase has a limitation of server side rules (although basic validation on access and data updates in the security rules console). There is a workaround to implement a server component (such as a nodejs client) that can listen to event updates and make post-facto updates to the firebase state e.g. set the highest bidder. This pattern is illustrated below:


ref: https://www-staging.firebase.com/resources/images/blog/client_server.png

Conclusion:

This is a first attempt at putting recent angular2 learning into practice but hopefully, this can provided you with enough motivation to embrace responsive programming and that you are as excited by the power of the reactive paradigm as I am. There will be more updates as I try to keep pace with angular and firebase developments, and learn more about reactive application architectural patterns within our projects.

No comments:

Post a Comment