限制API调用的抽象

时间:2014-03-16 22:34:28

标签: haskell monads

我试图编写一个方便的抽象来支持api调用。 API调用将是某种HTTP请求。我希望能够编写所有应用程序逻辑,就好像我只是在IO Monad中一样,然后让抽象方式限制我的调用,因此我不会超过某些预定义的限制。让这些调用以异步方式运行会很好。现在我有了这个。

data APICallStats = APICallStats                                                  
    {                                                                             
      startTime     :: !UTCTime                                                   
    , requestCount  :: !Int                                                       
    , tps           :: !Int                                                       
    } deriving Show                                                               

newtype APICall a = APICall (IO a)                                                

initStats :: Int -> IO APICallStats                                               
initStats limit = APICallStats <$> getCurrentTime <*> pure 0 <*> pure limit         


makeCall :: MVar APICallStats -> APICall a -> IO a                                  
makeCall mv (APICall f) = do                                                      
    (APICallStats st c t) <- readMVar mv                                          
    now <- getCurrentTime                                                         

    let inSeconds = realToFrac (diffUTCTime now st) :: Double                     
        cp1 = fromIntegral (c+1)                                                  
        td = fromIntegral t                                                       
    when (cp1 / inSeconds > td)                                                   
        (threadDelay (round $ (cp1 / td - inSeconds)*1000000))                    

    modifyMVar_ mv
         (\(APICallStats start req ts) -> return $ APICallStats start (req+1) ts)

    f

我可以像这样测试它。由于MVar,它在单个或多个线程中工作正常。

testCall :: String -> APICall ()                                                    
testCall id = APICall (getCurrentTime >>= (putStrLn . ((++) (id ++ " ")) . show)) 

test :: IO ()                                                                       
test = do                                                                           
    mv <- initStats 1 >>= newMVar                                                   
    forever $ makeCall mv (testCall "")                                             

threadedTest :: IO ()                                                               
threadedTest = do                                                                   
    mv <- initStats 1 >>= newMVar                                                   
    threadId <- forkIO $ forever $ makeCall mv (testCall "thread0")                 
    forever $ makeCall mv (testCall "main thread")                                  
    killThread threadId

这很好,除了它不像我想要的那样抽象。由于只有几个特定的​​API调用,我可以让它们返回类型APICall a。然后将其作为MonadIO实例的monad,这样我就可以编写逻辑并在需要时使用liftIO。但是我不确定我是否能够将这个单子作为一个没有违反法律以及如何去做的单子。

修改

我认为我已经非常接近我想要它做的事了。

withThrottle :: Int -> StateT (MVar APICallStats) IO a -> IO a                      
withThrottle limit f = do                                                           
    mv <- initStats limit >>= newMVar                                               
    evalStateT f mv                                                                 

process :: APICall a -> StateT (MVar APICallStats) IO a                             
process a = do                                                                      
    mv <- get                                                                       
    liftIO $ makeCall mv a

有了这个,我可以写这样的东西。

stateTest = do                                                                    
    withThrottle 2 $ do                                                           
        process (testCall "")                                                     
        process (testCall "")                                                     
        process (testCall "")                                                     
        liftIO $ threadDelay 10000000   -- Some long computation                                            
        process (testCall "")                                                       
        process (testCall "")                                                       
        process (testCall "")                                                       
        process (testCall "")

使用长时间运行的应用程序,整个事情可以运行的是这个monad。这将保证它在服务的生命周期内不会进行太多的外部API调用。我只是不想要国家monad。只是MonadIO的东西。所以我想我只需要隐藏它,我就会完成。但解决方案并不是非常漂亮,所以欢迎其他建议。

0 个答案:

没有答案