Come utilizzare un sistema di tipo strong per modellare i vincoli di business?

4

Seguendo il mio domanda ambigua , ecco una domanda che è probabilmente più mirata.

Considera che il seguente frammento di codice forma un programma Haskell:

data NightWatchCommand = InvalidCommand | DownloadCommand { url :: String } | PauseCommand { gid :: String } | UnpauseCommand { gid :: String } | StatusCommand { gid :: String } deriving (Show, Eq)
data AuthNightwatchCommand = AuthNightwatchCommand {
  command :: NightWatchCommand,
  user :: User
}

Ora, il vincolo aziendale che voglio applicare tramite il sistema dei tipi è questo: dovrebbe non essere possibile per creare un'istanza di NightwatchCommand non autenticata. E l'unico modo per creare un'istanza di AuthNightwatchCommand dovrebbe essere tramite una funzione speciale, ad esempio:

fromIncomingMsg :: String -> AuthNightwatchCommand

Solo per fornire un contesto più ampio, l'argomento stringa per questa funzione potrebbe essere:

status <some-id> <auth-token>

Ora, per complicare ulteriormente le cose, fromIncomingMsg deve convalidare <auth-token> dal DB. Il che significa che farà IO. Una firma di funzione più appropriata sarebbe:

fromIncomingMsg :: String -> IO (AuthNightwatchCommand)

A parte spingere questo in un modulo e nascondere i costruttori di dati, c'è un altro modo per farlo?

    
posta Saurabh Nanda 25.01.2016 - 19:18
fonte

3 risposte

4

Now, the business constraint I want to enforce via the type-system is this: it should not be possible to instantiate an unauthenticated NightwatchCommand.

Ora, forse mi manca un pezzo di contesto qui, ma questo mi lascia molto perplesso per il seguente motivo: perché un "vincolo di business" dovrebbe preoccuparsi di ciò che NightWatchCommand s viene istanziato o no? Mi sembra che il vincolo dovrebbe essere qualcosa di più simile a questo:

  • Dovrebbe essere impossibile per un utente non autenticato eseguire un comando di sorveglianza notturna.

E a mio avviso questo suggerisce un'organizzazione come la seguente:

module NightWatch (NightWatchCommand, execute) where

import Something.Auth

data NightWatchCommand = ...
data Result = ...

execute 
  :: Auth 
  -> NightWatchCommand 
  -> IO (Either InsufficientPermissions Result)
execute auth cmd = do 
  allowed <- checkAuth auth cmd
  if allowed
  then fmap Right (reallyExecute cmd)
  else Left (InsufficientPermissions $ "insufficient auth: " ++ (cmd, auth))

reallyExecute :: NightWatchCommand -> IO Result
reallyExecute cmd = ...

Fondamentalmente, quello che sto discutendo qui è che la sicurezza non fa parte del set di preoccupazioni di NightWatchCommand - le preoccupazioni principali per quel tipo sono proprio ciò che sono i comandi. La sicurezza è una preoccupazione per l' interprete che esegue i comandi. Se l'unico interprete fornisce richieste e verifica l'autenticazione e non consente ad altri moduli di scrivere i propri interpreti (non esportando i costruttori per il tipo NightWatchCommand ), tutti i chiamanti devono passare attraverso l'interprete.

    
risposta data 25.01.2016 - 20:54
fonte
0

Che ne dici di qualcosa di simile (testare questo codice è un esercizio per il lettore):

{-# LANGUAGE Rank2Types, GeneralizedNewtypeDeriving #-}

class Authorized a where
    fromIncomingMsg :: String -> IO (AuthNightwatchCommand)

instance Authroized AuthNightwatchCommand where
    fromIncomeMsg = error "Todo"

newtype Auth = Auth {unAuth :: forall a. Authorized a => a deriving (Authorized)}

--Please only use Auth

unAuth può convertire da Auth a AuthNightwatchCommand (ed è sicuro che altri lo possano usare).

    
risposta data 25.01.2016 - 20:03
fonte
0

La domanda in realtà non fornisce abbastanza informazioni, ma a giudicare dai commenti i tipi di fantoccio potrebbero essere (parte di) una soluzione:

data NightwatchCommand' auth = NightwatchCommand' NightWatchCommand

data IsAuthorised
data NotAuthorised

fromIncomingMsg :: String -> IO (NightwatchCommand' NotAuthorised)

withNightwatchCommand :: ?? -> NightwatchCommand' a
                            -> NightwatchCommand' a

withNightwatchCommandUnsafe :: ?? -> NightwatchCommand' a
                                  -> NightwatchCommand' NotAuthorised

authoriseNightwatch :: Auth -> NightwatchCommand' a
                            -> NightwatchCommand' IsAuthorised

protectedFunction :: NightwatchCommand' IsAuthorised -> StartMissilesFunction
    
risposta data 29.02.2016 - 00:19
fonte

Leggi altre domande sui tag