-
Notifications
You must be signed in to change notification settings - Fork 19
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Relaxing requireRecordFields = true
#61
Comments
Sorry, I read this in notifications at the weekend and then managed to forget about it... Firstly, thanks for taking the time to write, (and for realizing asking was the way to go; I've often gone deeper into rabbit holes before thinking!) The issue is that its important for the value that FsCodec produces Its the second bit that gets interesting:-
We def need the following to be supported in all cases:
I am not against providing a way for people to use string, bool and/or other roundtrippable values, but the constraints are:
So, unless we are talking something more limited - having FsCodec provide this as something that doesn't default to on, and you still want to do it, I'd ask that you integrate it with at least one of the stores in Equinox, and I'll do the others (unless you want to do them all, I'd be delighted if you did). If its just a matter of exposing the option for some usage outside of Equinox, then providing it as a not-on-by-default mode would make more sense. The final point is that supporting single field values like this is arguably a trap when it comes to versioning contracts.... If you have a record
... forcing people to use records leaves people in a better place from that perspective. Having said that, changing data contracts for events is generally something to avoid - in general minting a new |
I added a small test (and domain) in this commit. I'm not PRing that branch, it just serves as a scratchpad for now. Just seeking feedback before making a full-fledged PR. Here's the only (real) change in FsCodec: I'll make it more robust, of course, just including it so you can see what's up with the fsproj reference. |
That seems a reasonable test On the FsCodec side, the best thing to do is probably to add a new, optional Do you envisage replicating the test for the other stores too? (if you even get it basically working I'll run it against Cosmos if you don't have one to hand - unfortunately running against the emulator and/or an ephemeral Cosmos Container is not yet rigged in the build environment) The test case looks good, but I think I'd lose the 'snapshot' stuff as it dilutes the point of what you're doing? (will expand on that when there is an actual PR) |
By 'snapshot' stuff do you mean 'Initial/Active/Activate'? I wasn't sure how else to represent the start of a stream x| |
Yes, just saying you can prove the point for the purposes of the test case with less moving parts in the code - this is only a tiny nit point though We can finesse it in the context of the Equinox PR. |
Upon further thought, I think I can implement this feature in such a way that it upconverts well. Disregarding what's currently implemented in the linked PR, I might be able to create an (anonymous?) record at runtime (via reflection) to wrap the primitive/nonrecord type. Or, a simple dictionary with a single key/value might work. The (key) name can be provided by an attribute like so: type DuFieldNameAttribute (DuFieldName : string) =
inherit Attribute ()
type Event =
| [<DuFieldName("BranchId")>] DefaultBranchChanged of Guid (Unfortunately, it doesn't seem like we can put attributes on a du's fields.) The above event would then be serialized to {
"BranchId":"6C96B774-B4EC-4E19-925C-EFEC5EE6DC5D"
} In addition to the upconversion advantage, this type of implementation should be able to handle any type - not just selected primitives. My own project uses Nodatime - it would be nice if I could pass around a bare A disadvantage is that serialized events will be larger. |
Interesting. For Perhaps the upconversion mode could be represented by passing something like The size of events definitely doesn't matter. Regarding implementation technique, the Dictionary seems the most tempting, though a dynamically generated Struct Record would be a touch more efficient in terms of allocations (but you'd be having to dynamically name the field too) |
I think I need to modify the way TypeShape implements |
Hm, A large part of the value of UnionContract for me is that it lives elsewhere, and has clear docs, so I'd be reluctant. If you can distil the extension point you'd need in there to achieve your ends, I wouldn't rule out Eirik being amenable to it. More importantly, if you can show a prototype impl or explain the nature of a change you need, he may be able to suggest a better approach (I personally don't have an implementation approach of any kind jumping into my head!). I'm also struggling to remember the evolution and reasons why Eirik moved to majoring on unions - IIRC UnionEncoder once supported [named] tupled cases. IIRC he may have cut that feature due to usability concerns (people running into trouble making hard to version contracts due to having unnamed tuple cases which default to |
We can inject a custom However, the encoder isn't passed |
To be even more concrete, here's a commit with a proof of concept: dharmaturtle/TypeShape@43c9788 |
Nice - Well now, you can link to it in a question issue on the TypeShape repo ;) (I have not read enough TypeShape in recent times to meaningfully discuss it and/or comment on whether a cleaner solution exists) |
Well, you have an answer now ;P While part of me likes the idea of tuples and/or single field union cases as an on-ramp to records, like Eirik, I've seen the mess that can be caused too, and things do get hairy with converters etc (you only need to look at the fun of implementing FsCodec's UnionConverter for STJ and Newtonsoft to know its not something you want to get into if you can avoid) Overall the actual problem is I'm underwhelmed by the prospect of lumbering FsCodec maintainers (while that's presently me, that's more by accident than by design) maintaining a fork of UnionContractEncoder for the foreseeable future.
Have you tried using anonymous unions in your consumption scenario? (IME you typically end up just defining a record type in the end, but it does enable neat succinct modelling on the fly as you type) |
I completely agree with Eirik and definitely don't want to add to your workload for honestly what is a cosmetic improvement. As such I'm closing this issue. |
Returning to a different topic...
The module Events =
type Snapshot = // lots of stuff
module Fold =
type State =
| Initial
| Active of Events.Snapshot
| Deleted This is a pattern that I made up. Is there a better way to model the state of a stream/entity? I expect the answer is "it depends on your domain", but virtually all examples in Equinox set Is |
Good question! While that pattern can work, I'd be careful with it, and not let it drive my design... Firstly, while snapshots are free and/or a good idea in The FsCodec However, I'd tend to be very careful about having your model to be constrained to be something that must be able to be serialized directly as yours does Better to force yourself to not actually model stuff in your
The Having said all that, that general technique can work well for CRUDdy aggregates and lets you get that done without introducing new tech. The most obvious problem with it is that you will often end up adding I personally have never allowed myself to use the Snapshot event as a type in my state As usual with these things, I'd say the best thing is to get another set of eyes on the aggregate and/or the events. Sometimes things are just CRUD and not the interesting stuff where there's actually value in doing Real Modelling. Consider putting something more real on the DDD-CQRS-ES Slack if you can, or DM me, because the real answer is obviously It Depends, until its clear what the real use case is! |
Damn, forgot to hit Comment on this earlier...
Cool; I'm glad you took the time to explore rabbit hole nonetheless - time spent on pushing polish stuff like this is never entirely wasted IME. |
Hi!
Would you be interested if I opened a PR to relax
requireRecordFields = true
?FsCodec/src/FsCodec.NewtonsoftJson/Codec.fs
Lines 84 to 88 in 8f765f7
FsCodec/src/FsCodec.SystemTextJson/Codec.fs
Lines 54 to 56 in 8f765f7
To address your comments:
I don't disagree :) However, I am willing to shave this yak.
Many of my events simply contain a primitive, e.g.
It would simplify my code (and make it more implementation-agnostic) if I could refactor the above to
I'm not sure what a
d
field is. I'm currently only interested in relaxing the rules to includeGuid
s, but I could easily see this extending to other primitives.I'm totally okay with excluding
string
.I got this far with the implementation before realizing "I should probably ask first." >_>
The text was updated successfully, but these errors were encountered: