Given a ASP.NET MVC application with the following layers:
- UI (Views, CSS, Javascript, etc.)
- Controllers
- Services (Contains business logic, and data access)
The reason for no separate data access layer, is that I'm using SQL type provider.
(The following code may not be working, as it's only a raw draft).
Now imagine a service named UserService
defined like:
module UserService =
let getAll memoize f =
memoize(fun _ -> f)
let tryGetByID id f memoize =
memoize(fun _ -> f id)
let add evict f name keyToEvict =
let result = f name
evict keyToEvict
result
And then in my Controllers layer, I'll have another module named UserImpl
or it could just as well be named UserMemCache
:
module UserImpl =
let keyFor = MemCache.keyFor
let inline memoize args =
MemCache.keyForCurrent args
|> CacheHelpers.memoize0 MemCache.tryGet MemCache.store
let getAll = memoize [] |> UserService.getAll
let tryGetByID id = memoize [id] |> UserService.tryGetByID id
let add =
keyFor <@ getAll @> [id]
|> UserService.add MemCache.evict
The usage of this would be like:
type UserController() =
inherit Controller()
let ctx = dbSchema.GetDataContext()
member x.GetAll() = UserImpl.getAll ctx.Users
member x.UserNumberOne = UserImpl.tryGetByID ctx.Users 1
member x.UserNumberTwo = UserImpl.tryGetByID ctx.Users 2
member x.Add(name) = UserImpl.add ctx.Users name
Using interfaces, we would have the following implementation:
type UserService(ICacheProvider cacheProvider, ITable<User> db) =
member x.GetAll() =
cacheProvider.memoize(fun _ -> db |> List.ofSeq)
member x.TryGetByID id =
cacheProvider.memoize(fun _ -> db |> Query.tryFirst <@ fun z -> z.ID = ID @>)
member x.Add name =
let result = db.Add name
cacheProvider.evict <@ x.GetAll() @> []
result
And the usage would be something like:
type UserController(ICacheProvider cacheProvider) =
inherit Controller()
let ctx = dbSchema.GetDataContext()
let userService = new UserService(cacheProvider, ctx.Users)
member x.GetAll() = userService.GetAll()
member x.UserNumberOne = userService.TryGetByID 1
member x.UserNumberTwo = userService.TryGetByID 2
Obviously the interface implementation has much less code, but it doesn't really feel like functional code anymore. If I start using interfaces throughout my web app, when do I know when to use higher order functions instead? - else I'll just end up with a plain old OOP solution.
So in short: When should interfaces be used, and when to use higher order functions? - some line has to be drawn, or it will all be types and interfaces, whereof the beauty of FP disappears.