2011-10-18 18 views
5

Chcę utworzyć aplikację Happstack z dużą dostęp do bazy danych. Myślę, że monady Stos z IO w dolnej i zapisu jak Database monady na górze (z pisarza dziennika w środku) będzie pracować, aby mieć jasne funkcje w każdej dostępu, np:Jak utworzyć Monadę bazy danych w Happstack?

itemsRequest :: ServerConfig -> ServerPart Response 
itemsRequest cf = dir "items" $ do 
    methodM [GET,HEAD] 
    liftIO $ noticeM (scLogger cf) "sended job list" 

    items <- runDBMonad (scDBConnString cf) $ getItemLists 

    case items of 
    (Right xs) -> ok $ toResponse $ show xs 
    (Left err) -> internalServerError $ toResponse $ show err 

Z:

getItemList :: MyDBMonad (Error [Item]) 
getItemList = do 
    -- etc... 

Ale mam małą wiedzę monady i monada transformatorów (widzę to pytanie jako ćwiczenie do nauki o tym), a ja nie mam pojęcia, jak rozpocząć tworzenie bazy danych monady, jak podnieść IO z happstack do Stack bazy danych, ... itd.

+0

Próbowałem użyć "unsafePerformIO" do wykonania w nim IO. jak Happstack używa czysto kombinacji, może to twój jedyny sposób na zrobienie IO. – Nybble

+0

@Wu Xingbo, Możliwe jest wykonanie IO w happstack z liftIO, ale nie wiem, kto ma przejść do innego stosu monad. – Zhen

Odpowiedz

6

Prawdopodobnie chcesz użyć „ReaderT”:

type MyMonad a = ReaderT DbHandle ServerPart a 

transformator monada Reader sprawia pojedynczą wartość dostępnego za pomocą funkcji ask - w tym przypadku wartość chcemy żeby wszyscy się na to połączenie bazy danych .

Tutaj, DbHandle jest pewne połączenie z bazą danych.

Ponieważ "ReaderT" jest już instancją wszystkich klas typu happstack-server, wszystkie normalne funkcje happstack-server będą działać w tej monadzie.

Prawdopodobnie też chcą jakąś pomocnika, aby otworzyć i zamknąć połączenie z bazą danych:

runMyMonad :: String -> MyMonad a -> ServerPart a 
runMyMonad connectionString m = do 
    db <- liftIO $ connect_to_your_db connectionString 
    result <- runReaderT m db 
    liftIO $ close_your_db_connection db 

(To może być lepiej użyć funkcji takich jak „” wspornika tutaj, ale nie wiem, że istnieje jest taka operacja dla monitory ServerPart)

Nie wiem, jak chcesz rejestrować - w jaki sposób planujesz interakcję z plikiem dziennika? Coś jak:

type MyMonad a = ReaderT (DbHandle, LogHandle) ServerPart a 

a następnie:

askDb :: MyMonad DbHandle 
askDb = fst <$> ask 

askLogger :: MyMonad LogHandle 
askLogger = snd <$> ask 

może być za mało. Można wtedy budować na tych prymitywach, aby tworzyć funkcje wyższego poziomu. Będziesz także musiał zmienić runMyMonad, aby przejść w LogHandle, cokolwiek to jest.

Gdy uzyskasz więcej niż dwie rzeczy, do których chcesz mieć dostęp, opłaca się mieć odpowiedni typ rekordu zamiast krotki.

+2

Tematem pobocznym: dla łączenia połączeń jest http://hackage.haskell.org/package/resource-pool i http://hackage.haskell.org/package/pool. To może być więcej niż potrzebujesz. –

+0

dzięki za końcówkę puli! – Zhen

6

Oto minimalny działający kod skompilowany z fragmentów powyżej dla zdezorientowanych początkujących, takich jak ja.

Umieszczasz rzeczy w typie AppConfig i chwytasz je za pomocą ask wewnątrz swoich twórców odpowiedzi.

{-# LANGUAGE OverloadedStrings #-} 
module Main where 

import Happstack.Server 
import Control.Monad.Reader 
import qualified Data.ByteString.Char8 as C 

myApp :: AppMonad Response 
myApp = do 
    -- access app config. look mom, no lift! 
    test <- ask 

    -- try some happstack funs. no lift either. 
    rq <- askRq 
    bs <- lookBS "lol" 

    -- test IO please ignore 
    liftIO . print $ test 
    liftIO . print $ rq 
    liftIO . print $ bs 

    -- bye 
    ok $ toResponse ("Oh, hi!" :: C.ByteString) 

-- Put your stuff here. 
data AppConfig = AppConfig { appSpam :: C.ByteString 
          , appEggs :: [C.ByteString] } deriving (Eq, Show) 
config = AppConfig "THIS. IS. SPAAAAAM!!1" [] 

type AppMonad = ReaderT AppConfig (ServerPartT IO) 

main = simpleHTTP (nullConf {port=8001}) $ runReaderT myApp config {appEggs=["red", "gold", "green"]} 
Powiązane problemy