My abstract goals are:
1) Create a CRUD API That is a wrapper of 3rd party library with CRUD interface(could be from service object and/or entity object)
2) Each CRUD method should limit the types fed to it based on predefined types definitions (one type can be fed to multiple methods)
3) When User chose a specific type of a method he should be forced to insert the right types of other parameters(like a key) based on the chosen type, And those should be validated in compile time(passing key as object type require runtime evaluation for the "real" type of the object).
4) The method's types is a 3rd party interfaces that are not in my control and I cannot alter them.
5) The API should be straightforward for the user with the less amount of boilerplate code for him.
One way that I found to solve this problem in C# is:
public interface Update<T,TKey> {}
public interface Add<T> {}
public interface Delete<T,TKey> {}
public interface Get<T,TKey> {}
public class Invoice:Get<string>, Add<ThirdPartyInvoice>, Update<ThirdPartyInvoice,string> {}
//More types can come here...
public static class CRUDAPI
{
public static T Get<T,TKey>(Get<T,TKey> type, TKey key)
{
//will get a T from a service object based on TKey
}
public static Unit Add<T>(Add<T> type, Func<T,T> select)
{
//will get a new instance of T and will feed it to the select function.
//and then will feed the result to the 3rd party add method
}
public static Unit Update<T,TKey>(Update<T,TKey> type,TKey key, Func<T,T> select)
{
//will load an instance of T and will feed it to the select function.
//and then will feed the result to the 3rd party update method
}
public static Unit Delete<T,TKey>(Delete<T,TKey> type,TKey key)
{
//will load an instance of T and then will use 3rd party delete method
}
}
User can then use it like:
Add(new Invoice(), inv=> { inv.field1 = "123"; ... return inv;})
What could be a good way to solve this problem in a functional style(in F# for example)?
In functional programming you would solve that problem with generic algebraic types. You'll normally would decide to deal with a "command" primitive like:
data Command a b c = Command(a -> Either b c)
It is up to you to find a right balance between command's specificity, number of generic parameters, and general (in-out) interface: though Either is a classical choice, also available in F#; the other commonly used type is Maybe, also provided by F#.
Then you can define:
data CRUD a b c d e f = CRUD{ create :: Command a b c, read :: Command d e f, ... }
Once again, find the right balance.
Then you figure out, that there are lots and lots of commands. Really. Some of them go directly to the database, other responsible for DTO -> Model mappings, other deal with validation. You'd like to compose them in a LEGO-fashion: Command a b c -> Command b e d -> Command a e d
, which can be solved by extending your Command
algebraic type: ... | CompositeCommand(a -> Either b c, b -> Either e d)
, which internally will rely on the Either
's monadic binding to chain both functions.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With