Should IResource be "dumb" or smart?
See original GitHub issueWith the imminent merging of the interface branch, we currently have the following pattern which has become much clearer as part of that refactor:
IResource
. Represents an ARM resource in F#. Examples include:
Network
PublicIPAddress
VirtualNetwork
NetworkInterface
Web
ServerFarm
Sites
An IResource is typically a record and has two members - a Resource Name (used to detect duplicate resources) and a unit -> obj
function which knows how to create a “JSON ready” object containing only primitives ready for serialization.
IResourceBuilder
(andResourceBuilder
). Given a location, generate a list of IResource objects. ResourceBuilders are typically Records (typically with the postfixConfig
) and are wrapped by computation expressions builders (ignore the new ResourceBuilder approach for this discussion).
The pattern looks something like this:
So what?
ResourceBuilders are typically strongly typed, using discriminated unions and the like in order to construct types which, more or less, make illegal states unrepresentable. I say “more or less”, because there are some compromises that have made in order to keep the API as simple as possible from a consumption point of view (so that a minimal amount of types are needed when consuming any builders etc.).
The question is what constraints should the IResource make (before JSON serialization occurs)?
Option 1: Make them simple types with primitives only
This makes the mapping to the JSON-ready object very simple as the values are already primitives. The mapping instead happens in the IResourceBuilder at the point of generating the IResources. The “cost” of this is that if you want to use the same IResource type in multiple builders - for example, an IPAddress or a ServerFarm - you have to do the same mapping from a “smart” IResourceBuilder to a “dumb” IResource.
This is the current approach we now have in the latest version of master.
Option 2: Make them smarter types.
We make IResources smarter and put richer types on them. The cost of “dumbing down” happens right at the end, when we move to the JSON-ready object, and only then. The benefit of this approach is that the mapping to JSON is guaranteed to happen in a typesafe manner, and the mapping from IResourceBuilders to IResources will be relatively simple. The concern is that, assuming that we share typed because the IResource and IResourceBuilders, then the types that these records have should still live at a level that the consumer can easily get to - typically, just by opening Farmer.Resources
(or even better, just Farmer
). If these types instead live next to the IResource, users will need to fumble around knowing which namespaces to open, and the whole exercise of “ease of use” (which is a key driver behind Farmer) will be lost.
Appreciate this might seem like bikeshedding but nonetheless, if you’re interested in this, I’d welcome thoughts and feedback.
A good example is to look at ExpressRouteCircuit
in the current master, which has been completely “dumbed down” as part of my merge into the new API cc: @ninjarobot . Previously it had stronger types.
Issue Analytics
- State:
- Created 3 years ago
- Reactions:1
- Comments:11 (10 by maintainers)
Top GitHub Comments
You forgot the sarcasm font 😛
Type-safety, building blocks, and seams
I do not think that giving users the option of using an
IResource
a problem, as long as we do provide higher-level constructs that do offer that ie. theConfig
andBuilders
. And maybe this is where we are diverging on view…Currently, I see
IResource
as a raw persistence/transport implementation. It is a seam. Imagine for example we needed to support different ARM template versions and a builder would pick the IResource to output. Or Azure releases YARML 😛 Another desire for seems may come from a particularly tricky mapping. You might want to switch from a more testing Builder -> Json and split them Builder -> IResource; IResouce -> Json.Viewing it this way makes
IResource
an implementation detail but one that we surface as a fairly first-class citizen purely because this is a library, not a framework (?). Maybe we are diverging in opinion here though, as I know you wanted to make Farmer opinionated.What I am suggesting is let’s have the low-level building blocks, the clean type-safe wrappers and syntactic niceness around the building blocks of Azure, and then tighten the opinions in the “topology” layer.
Naming
The naming is a little confusing I think because we have 2 layers that represent the same thing. Maybe this is a tell and we need to simplify but for the sake of exploring the possible layers, let us push on.
Some suggestions for sharpening up the naming:
IResource
:IArmResource
brings it back to it being specifically for outputting the format for the resource in the ARM template. Other words that might fit to make this specific ResourceTemplate, ResourceSchema, …?Making it simple to have the building blocks so people could create their own Recipes would be awesome. I mean that is already possible really but even in naming, we could show that this is the intent. I have worked in big dev departments were rolling out like this in a parameterized way would have been awesome.