Thursday, August 29, 2013

Using Amazon Web Services with TCache and MFlow

The code of this example is identical to any one for persistence in files that you can find here except the procedure  setAmazonSimpleDB that set the default persistence in Amazon simpleDB. 

Here is the example running:

http://mflowdemo.herokuapp.com/noscript/database

As ever, MFlow permits to address all the pages in the flow with a single bookmark. You just enter the appropriate path and parameters. For example this URL:

http://mflowdemo.herokuapp.com/noscript/database/newtext

Present the entry form, that is the second page in the demo. And this URL:

http://mflowdemo.herokuapp.com/noscript/database/newtext?p0= text to enter

Fill the text box of the entry form, insert the text in the database and return to the first page again, since the demo has a loop.

Default persistence means that each register is stored in a small blob somewhere, either in a file -by default- or in another storage. For small blobs identified by keys,  Amazon simpleDB is better than  S3. 

The default persistence is defined in the setDefaultPersist and setPersist. While the former -as its name says- set the default for any kind of data, this latter is per-datatype. It is a method defined in the Serializable instance in the DefaultPersistence module. The blob of each register is read and written by TCache and converted to a haskell register under a  DBRef reference, in the STM monad. DBRefs follows the semantics of the TVar references.

TCache besides caching the registers, it permits querying and indexing them by a relational-like syntax using haskell register field names (see the query defined below in allTexts). The indexes used for querying are also registers that are stored and retrieved using the default persist mechanism.  That means that the registers that are not used are discarded from the cache, and the modified indexes are stored in the persistent storage automatically, in this case, in SimpleDB

Caching and querying local indexes improves response time and reduces the number of accesses to the paid cloud infrastructure, so it reduces costs.

I´m not fully satisfied with the default persistence set in setAmazonSimpleDB  I need to optimize the storage and the access using more features of  AWS simpleDB and mix it with Amazon S3 for largue blogs, but this is a proof of concept.




This is the code:
{-# LANGUAGE DeriveDataTypeable, RecordWildCards #-}
module Database where
import MFlow.Wai.Blaze.Html.All hiding (select)

import Data.Typeable
import Data.TCache.IdexQuery
import Data.TCache.DefaultPersistence
import Data.TCache.Memoization
import Data.Monoid
import Menu 
import Data.String
import Aws
import Aws.SimpleDb hiding (select)
import qualified Data.Text as T
import Data.Text.Encoding
import Data.ByteString.Lazy(toChunks,fromChunks)
import Network

import Debug.Trace
(!>)= flip trace

data  MyData= MyData{idnumber ∷ Int, textdata ∷  T.Text} deriving (Typeable, Read, Show)      -- that is enough for blob persistence

instance Indexable MyData where  
   key=  show . idnumber    -- to notify what is the key of the register
   defPath = const ""
   
data Options= NewText  | Exit deriving (Show, Typeable) 


main= do
  setAmazonSimpleDB
  syncWrite  $ Asyncronous 120 defaultCheck  1000
  index idnumber
  runNavigation "" $ transientNav $ do
     all ←  allTexts
     r ←  ask $ listtexts all
     case r of
         NewText → do
              text ←  ask $   p << "insert the text" 
                           ++> getMultilineText "" <++ br
                           <** submitButton "enter"

              -- store the name in the cache 
              -- (later will be written to disk automatically)
              liftIO . atomically . newDBRef $ MyData (length all) text 

         Exit → return ()
     where 

     menu=   wlink NewText   << p << "enter a new text" <|> 
             wlink Exit      << p << "exit to the main menu"
               
     listtexts all  =  do
           h3 << "list of all texts"
           ++> mconcat[p <<  t | t ←  all]
           ++> menu
           <++ b << "or the back button for a new database action"

     allTexts= liftIO . atomically . select textdata $ idnumber .>=. (0 ∷  Int) 
     
sdbCfg =  defServiceConfig

domain = fromString "mflowdemotest"

setAmazonSimpleDB = withSocketsDo $ do
 cfg ←  baseConfiguration
-- simpleAws cfg sdbCfg $ deleteDomain domain 
 simpleAws cfg sdbCfg $ createDomain domain  -- delete once created
 setDefaultPersist $ Persist{
   readByKey= λkey → withSocketsDo $ do
       r ←  simpleAws cfg sdbCfg $ getAttributes (T.pack key) domain 
       case r of
        GetAttributesResponse [ForAttribute _ text] → return $ Just   
             $ fromChunks [encodeUtf8 text]
        _ → return Nothing,
        
   write= λkey str → withSocketsDo $ do
       simpleAws cfg sdbCfg 
                     $ putAttributes  (T.pack key)  
                       [ForAttribute tdata 
                        (SetAttribute 
                          (T.concat $ map decodeUtf8 $ toChunks str)
                             True)] domain
       return (),
   delete= λkey    → withSocketsDo $ do
     simpleAws cfg sdbCfg 
     $ deleteAttributes (T.pack key)  
       [ForAttribute tdata DeleteAttribute] domain
     return ()
     }


tdata= fromString "textdata"

No comments: