Skip to content
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

Event Name Serialization #6

Open
9 tasks done
CumpsD opened this issue Nov 10, 2020 · 4 comments
Open
9 tasks done

Event Name Serialization #6

CumpsD opened this issue Nov 10, 2020 · 4 comments
Assignees
Labels
address Things related to the address registry. bug Something isn't working building Things related to the building registry. municipality Things related to the municipality registry. parcel Things related to the parcel registry. postal Things related to the postal registry. streetname Things related to the streetname registry.

Comments

@CumpsD
Copy link
Contributor

CumpsD commented Nov 10, 2020

The case of [EventName] and Syndication Feeds

Context

In our usage of Event Sourcing, we keep the event name as a column in our Events to allow for future deserialisation of the event back into an object. In a very early version, we naively took the class name as the event name. Not long afterwards we introduced an EventNameAttribute to turn this into a fixed value, which would survive refactoring of our code.

For example:

[EventTags(Tags.Sync)]
[EventName("MunicipalityBecameCurrent")]
[EventDescription("De gemeente werd in gebruik genomen.")]
public class MunicipalityBecameCurrent : IHasProvenance, ISetProvenance

If this class ever had to be renamed to MunicipalityBecameCurrentV1, it would not break our deserialisation, because MunicipalityBecameCurrent would be the stored key.

Over time, this attribute has taken on other usages as well, for example in documenting all our events with Structurizr, together with EventDescription.

For the most part of the development, events have remained an implementation detail and were not exposed to the outside world. Until a feature request came along to allow our user to get a feed of events. The use case is to allow them to built anything they want based on the events.

This resulted in the creation of the Feeds endpoints. These endpoints are populated by Syndication projections in each registry. In these projections SetEventData is responsible for populating the XML representation of the event for outside consumption. The feed endpoints simply return these pre-serialised events to the consumer.

Problem

Whereas we used the EventNameAttribute for de/serialisation in the Event Store, we made the mistake of using the following code in the syndication projections:

syndicationItem.EventDataAsXml = message.ToXml(message.GetType().Name).ToString(SaveOptions.DisableFormatting);

This means the class name is used in the feeds instead of the attribute name. In most cases these were identical, causing the late detection of this bug, but in some cases they were different:

[EventName("MunicipalityFacilityLanguageWasAdded")]
[EventDescription("Een faciliteiten taal van de gemeente werd toegevoegd.")]
public class MunicipalityFacilitiesLanguageWasAdded : IHasProvenance, ISetProvenance

This resulted in an XML element having MunicipalityFacilitiesLanguageWasAdded as a root tag instead of MunicipalityFacilityLanguageWasAdded, while also being vulnerable to breaking changes when refactoring class names.

Solution

A solution for this has been implemented in Informatievlaanderen/municipality-registry#107 which does not use the class name anymore, but uses the EventNameAttribute.

=> syndicationItem.EventDataAsXml = message.ToXml(message.GetType().GetCustomAttribute<EventNameAttribute>()!.Value).ToString(SaveOptions.DisableFormatting);

This fix has to be implemented in every registry separately.

Update:
While fixing it, we did not have to use reflection to get the attribute, the event name was already present in the event itself.

public static void SetEventData<T>(this MunicipalitySyndicationItem syndicationItem, T message, string eventName)
    => syndicationItem.EventDataAsXml = message.ToXml(eventName).ToString(SaveOptions.DisableFormatting);

Additional remarks

  • With this fix in place, there is no requirement to make sure the class name is equal to the EventNameAttribute.
  • In the current cases where the name is different, it is a breaking change. Since this functionality is not yet in production, we do not need to version this change.

Todo

Determine in which registers this occurs to rebuild the projections. Finding this can be done with a unit test:

[Fact]
public void HasEventNameAttributeEqualToClass()
{
    foreach (var type in _eventTypes)
        type
            .GetCustomAttribute<EventNameAttribute>(true)!.Value
            .Should()
            .Match(x => x == type.Name);
}

Registries to check

  • Municipality
  • Postal
  • StreetName
  • Address
  • Building
  • Parcel

Rebuilds Required

  • Municipality
  • Address
  • Building
@CumpsD CumpsD added bug Something isn't working municipality Things related to the municipality registry. postal Things related to the postal registry. streetname Things related to the streetname registry. address Things related to the address registry. building Things related to the building registry. parcel Things related to the parcel registry. labels Nov 10, 2020
@CumpsD
Copy link
Contributor Author

CumpsD commented Nov 12, 2020

Fixes

SQL Fixes

Address

UPDATE [address-registry].AddressRegistryLegacy.AddressSyndication
   SET EventDataAsXml = REPLACE(REPLACE(EventDataAsXml, '</AddressPersistentLocalIdWasAssigned>', '</AddressPersistentLocalIdentifierWasAssigned>'), '<AddressPersistentLocalIdWasAssigned>', '<AddressPersistentLocalIdentifierWasAssigned>')
 WHERE [changetype] = 'AddressPersistentLocalIdentifierWasAssigned' AND EventDataAsXml LIKE '<AddressPersistentLocalIdWasAssigned>%'

Building

UPDATE [building-registry].BuildingRegistryLegacy.BuildingSyndication
   SET
EventDataAsXml = 
(CASE
	WHEN [changetype] = 'BuildingPersistentLocalIdentifierWasAssigned' THEN REPLACE(REPLACE(EventDataAsXml, '</BuildingPersistentLocalIdWasAssigned>', '</BuildingPersistentLocalIdentifierWasAssigned>'), '<BuildingPersistentLocalIdWasAssigned>', '<BuildingPersistentLocalIdentifierWasAssigned>')
	WHEN [changetype] = 'BuildingUnitPersistentLocalIdentifierWasAssigned' THEN REPLACE(REPLACE(EventDataAsXml, '</BuildingUnitPersistentLocalIdWasAssigned>', '</BuildingUnitPersistentLocalIdentifierWasAssigned>'), '<BuildingUnitPersistentLocalIdWasAssigned>', '<BuildingUnitPersistentLocalIdentifierWasAssigned>')
	WHEN [changetype] = 'BuildingUnitPersistentLocalIdentifierWasDuplicated' THEN REPLACE(REPLACE(EventDataAsXml, '</BuildingUnitPersistentLocalIdWasDuplicated>', '</BuildingUnitPersistentLocalIdentifierWasDuplicated>'), '<BuildingUnitPersistentLocalIdWasDuplicated>', '<BuildingUnitPersistentLocalIdentifierWasDuplicated>')
	WHEN [changetype] = 'BuildingUnitPersistentLocalIdentifierWasRemoved' THEN REPLACE(REPLACE(EventDataAsXml, '</BuildingUnitPersistentLocalIdWasRemoved>', '</BuildingUnitPersistentLocalIdentifierWasRemoved>'), '<BuildingUnitPersistentLocalIdWasRemoved>', '<BuildingUnitPersistentLocalIdentifierWasRemoved>')
END)
 WHERE ([changetype] = 'BuildingPersistentLocalIdentifierWasAssigned' AND EventDataAsXml LIKE '<BuildingPersistentLocalIdWasAssigned>%')
    OR ([changetype] = 'BuildingUnitPersistentLocalIdentifierWasAssigned' AND EventDataAsXml LIKE '<BuildingUnitPersistentLocalIdWasAssigned>%')
    OR ([changetype] = 'BuildingUnitPersistentLocalIdentifierWasDuplicated' AND EventDataAsXml LIKE '<BuildingUnitPersistentLocalIdWasDuplicated>%')
    OR ([changetype] = 'BuildingUnitPersistentLocalIdentifierWasRemoved' AND EventDataAsXml LIKE '<BuildingUnitPersistentLocalIdWasRemoved>%')

@ArneD
Copy link
Member

ArneD commented Nov 12, 2020

Address fix: Informatievlaanderen/address-registry#214

AddressPersistentLocalIdWasAssigned => AddressPersistentLocalIdentifierWasAssigned
Rebuild or SQL replace needed

@ArneD
Copy link
Member

ArneD commented Nov 12, 2020

Informatievlaanderen/building-registry#185
Building: rebuild or sql replace needed

BuildingPersistentLocalIdWasAssigned => BuildingPersistentLocalIdentifierWasAssigned
BuildingUnitPersistentLocalIdWasAssigned => BuildingUnitPersistentLocalIdentifierWasAssigned
BuildingUnitPersistentLocalIdWasDuplicated => BuildingUnitPersistentLocalIdentifierWasDuplicated
BuildingUnitPersistentLocalIdWasRemoved => BuildingUnitPersistentLocalIdentifierWasRemoved

@CumpsD
Copy link
Contributor Author

CumpsD commented Nov 30, 2020

We need to make sure the receiving ends also use the correct names.

To check:

  • Municipality
  • Postal
  • StreetName
  • Address
  • Building
  • Parcel

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
address Things related to the address registry. bug Something isn't working building Things related to the building registry. municipality Things related to the municipality registry. parcel Things related to the parcel registry. postal Things related to the postal registry. streetname Things related to the streetname registry.
Projects
None yet
Development

No branches or pull requests

2 participants