diff --git a/README.md b/README.md index a2b907f3..fd5264d3 100644 --- a/README.md +++ b/README.md @@ -381,11 +381,13 @@ The de-facto standard Event Store [EventStore.org](https://eventstore.org) and i Where: -- `{CategoryName}` represents a high level contract/grouping; all stream names starting with `Category-` are the same category +- `{CategoryName}` represents a high level contract/grouping; all stream names starting with `Category-` are the same category. Must not contain any `-` characters. - `-` (hyphen/minus) represents the by-convention standard separator between category and identifier -- `{Identifier}` represents the identity of the Aggregate / Aggregate Root instance for which we're storing the events within this stream. It should not embed `-` characters (but see below re usage of `_` to manage a composite id). Note `StreamName` will actively reject such values by throwing exceptions when fields erroneously have embedded `-` or `_` values (see xmldoc on the relevant functions and/or [look at the code](https://github.com/jet/FsCodec/blob/master/src/FsCodec/StreamName.fs)). +- `{Identifier}` represents the identity of the Aggregate / Aggregate Root instance for which we're storing the events within this stream. The `_` character is used to separate composite ids; see [the code](https://github.com/jet/FsCodec/blob/master/src/FsCodec/StreamName.fs). -Its important to note that composing a stream name directly from values in the Domain that might include characters such as `-` (which by default drives `$ec` indexing in EventStoreDB) and/or arbitrary Unicode chars (which may not work well for other backing stores e.g. if CosmosDB were to restrict the character set that may be used for a Partition Key) is not a good idea in general (you'll also want to ensure its appropriately cleansed, validated and/or canonicalized to cover SQL Injection and/or XSS concerns). In short, no, you shouldn't just stuff an email address into the `{Identifier}` portion. +The `StreamName` module will reject invalid values by throwing exceptions when fields have erroneously embedded `-` or `_` values. + +It's important to apply some consideration in mapping from values in your domain to a `StreamName`. Domain values might include characters such as `-` (which may cause issues with EventStoreDb's [`$by_category`](https://developers.eventstore.com/server/5.0.8/server/projections/system-projections.html#by-category) projections) and/or arbitrary Unicode chars (which may not work well for other backing stores e.g. if CosmosDB were to restrict the character set that may be used for a Partition Key). You'll also want to ensure it's appropriately cleansed, validated and/or canonicalized to cover SQL Injection and/or XSS concerns. In short, no, you shouldn't just stuff an email address into the `{Identifier}` portion. [`FsCodec.StreamName`](https://github.com/jet/FsCodec/blob/master/src/FsCodec/StreamName.fs): presents the following set of helpers that are useful for splitting and filtering Stream Names by Categories and/or Identifiers. Similar helpers would of course make sense in other languages e.g. C#: diff --git a/src/FsCodec/StreamName.fs b/src/FsCodec/StreamName.fs index efbef3bf..5cf3bcc0 100755 --- a/src/FsCodec/StreamName.fs +++ b/src/FsCodec/StreamName.fs @@ -25,7 +25,7 @@ module StreamName = /// category is separated from id by `-` let create (category : string) aggregateId : StreamName = if category.IndexOf '-' <> -1 then invalidArg "category" "may not contain embedded '-' symbols" - UMX.tag (sprintf "%s-%s" category aggregateId) + UMX.tag (sprintf "%s-%s" category aggregateId) /// Composes a StreamName from a category and > 1 name elements. /// category is separated from the aggregateId by '-'; elements are separated from each other by '_' @@ -44,7 +44,7 @@ module StreamName = /// Throws InvalidArgumentException if it does not adhere to that form let parse (rawStreamName : string) : StreamName = if rawStreamName.IndexOf('-') = -1 then - invalidArg (sprintf "Stream Name '%s' did not contain exactly one '-' separator" rawStreamName) "streamName" + invalidArg (sprintf "Stream Name '%s' must contain a '-' separator" rawStreamName) "streamName" UMX.tag rawStreamName (* Parsing: Raw Stream name Validation functions/pattern that handle malformed cases without throwing *) diff --git a/tests/FsCodec.Tests/StreamNameTests.fs b/tests/FsCodec.Tests/StreamNameTests.fs index 358d70af..373481f9 100644 --- a/tests/FsCodec.Tests/StreamNameTests.fs +++ b/tests/FsCodec.Tests/StreamNameTests.fs @@ -29,3 +29,8 @@ let [] ``Can roundtrip single aggregateIds with embedded dashes and unders test <@ let (StreamName.CategoryAndIds (scat, aggIds)) = sn scat = cat && ["a-b";"c-d"] = List.ofArray aggIds @> + +let [] ``StreamName.parse throws given 0 separators`` () = + raisesWith + <@ StreamName.parse "Cat" @> + (fun e -> <@ e.Message = "streamName (Parameter 'Stream Name 'Cat' must contain a '-' separator')" @>)