Meta data in event and command messages - domain-driven-design

I am curious to find out how you guys are incorporating meta data about command/event messages in a cqrs solution. For example, I want to know who, when, which host, etc. generated the command. I don't want to put these into message itself.
Say in a web app, user created a shopping cart CreateShoppingCart { CartID, UserID }. Then added items to it, AddItem { CartID, ItemID, Amount, etc }. I want to record exacty when the used clicked the "Add To Cart" button.
I can add this into some Dictionary<string, object> Headers { get;
set; } property. That property could be in a BaseMessage class.
I can rely on the messaging framework (something like NServiceBus) and add this data into the message header in the message context.
Send seperate command for this info. Something like LogCommandDetails { CommandID: 'id of AddItem command', DateTime, Some other meta data }. When this comamnd is handled, I can update the projection of ItemAdded event and add this data into the projection.
What are your thoughts?
Thanks

Typically this information is stored in message headers, which is option 2. This is exactly what message headers are for. Note, there is a subtle difference between a message from perspective of a messaging framework and a message in your domain which is the body of the message in a messaging framework.
However, it can be difficult to discern what is data and what is metadata. I run into this issue with dates, among other things. For example, is a timestamp associated with an event metadata or proper domain data? What if the timestamp is required for execution of certain business logic? In your example, do you need to record the date for reporting or audit purposes, or is the date needed for the domain to function? In the former case, use headers, in the latter, place the date in the message body.

Related

Changing product price in Shopware 6 dynamically

I would like to change the price of a product based on the customer's selection. For example, I'm trying to build a small PDP widget to make customers able to choose the number of candles on a cake or write text on cakes and update the price accordingly. The docs only cover how to change the price by overwriting the cart's collector/processor but I don't want to use this method because of other plugins potentially overwriting the same service. So, is there are any other methods of changing the price of the products by subscribing to an event?
There are a few things you will need to consider in this one.
Firstly, you will need to save the user input data somewhere (amount of candles, text).
Possibly a separate database table that has a OneToMany relationship on cart line items. See this article. Und ya, this is also the part where you will hook into the onLineItemAdd event & save your user input to that table. You may as well also subscribe to the onLineItemUpdate for the same saving logic. You could do the same when removing the item from the cart, although that may not be necessary if you use database's "CASCADE on delete" when implementing your DB table. Meaning once the line item gets removed by the customer (deleted in the DB), your database entry gets deleted as well.
Afterwards, you can then use the extensions or otherwise called associations to pull this data on the cart page & the order pages. You can be a little more fancy here, if you look at all the frontend router calls, you will notice that Shopware sometimes passes "Criteria" class you can hook into.
public static function getSubscribedEvents(): array
{
return [
OrderRouteRequestEvent::class => 'alterCriteria',
DocumentOrderCriteriaEvent::class => 'alterCriteria',
];
}
public function alterCriteria(Event $event): void
{
if (method_exists($event, 'getCriteria')) {
$event->getCriteria()->addAssociation('lineItems.myExtension'); // check syntax, could be lineItem.
}
}
Now you can extend the twig templates to show your candles or text in the order page, cart page, document (invoice) pages.
Secondly, you will have to handle the price. This part will be easier now that you have data saved & being automatically pulled via criteria subscribers. If it's not possible to hook into those events all the time, you will still have an option to manually load your data.
I do not recommend modifying the price itself, but maybe you could look into adding a surcharge instead. Maybe this article will be helpful to understand the price flow. You could also see if there are any other plugins out there that implement surcharge logic to see it in action.

Threaded messaging on pubnub

We want to create threads for messages within a chat channel on Pubnub. For example, someone could respond to a specific message in a channel by 'creating a thread' and starting to chat. Is there a prescribed way to model this behavior? If so, can you please reference documentation?
This is the behavior you see in slack, for reference.
There's no ready-made solution documented for threaded messaging. However, building a hierarchical relationship between messages could be achieved by tagging them with metadata (using PN Objects and/or MessageActions) and then some coding on your end to maintain and handle their relationship.
You could use the time token of the thread's first message as the key, group messages based on it, and use the messages' own time tokens to generate the order for the UI.
https://www.pubnub.com/docs/sdks/javascript/api-reference/publish-and-subscribe#methods
Here's the high-level design for doing this:
A message is published to a channel with the name chat_11223344 (channel name uses chat_ as a prefix for all chat channels and a generated id - keeping it short here but you can use a uuid generator for this). That publish returns a publish timetoken, something like this: 16183330926487763.
Using PN Objects, your display name for the channel can be set along with a description.
In your chat UI, you allow a person to create a thread on that message. The message gets published to a channel named chat_11223344.16183330926487763 , using the publish timetoken of the top-level message as the "sub-channel" name.
So that you can easily identify top-level messages that are threaded, you would add a MessageAction to that message when the first "threaded" message is published. You may also want to add custom Channel Metadata (PN Objects, again) to add a "isThreaded":true key/value.
So with PubNub you can append meta data to either the message itself or PubNub has a section called meta (https://www.pubnub.com/docs/sdks/javascript/api-reference/publish-and-subscribe#methods).
An example payload could be:
{
"type":"message",
"payload":"What do people want for lunch? Pizza?",
"sender":"me",
"sent":1618336638,
"messageActive":true,
"channel":"main",
"messageID":"main.abc123"
}
where abc123 is a uuid that references that message.
When someone wants to thread a message you can append "threaded":true variable to the object.
{
...
"messageID":"main.abc123",
"threaded":true,
...
}
Now your UI knows that there is a breakout thread, using main.abc123.thread as the channelID for that specific thread.
Your app then subscribes to the new channel main.abc123.thread and you can use fetchMessages(); to get history messages as well as new real time messages.

Sending push notification on firestore document field change

I have a Flutter app that lets users rent items from eachother with Firestore RTDB. In my rental document, I have a field status that determines the status of the rental (think of it like shipping an item, where items can have status of 'ordered', 'shipped', 'delivered' etc). My status variable is a number between 0 and 5, and each number represents a different phase. When the status variable changes, I want to notify the other user in the rental with a push notification. But I don't know which of the following methods is best.
The first way is to use a cloud function that triggers every time the rental document is updated. But I only check for the status field. It would look something like this:
exports.notify = functions.firestore.document('rentals/{rentalId}')
.onUpdate(async (snapshot, context) => {
const oldSnap = snapshot.before.data(); // previous document
const newSnap = snapshot.after.data(); // current document
// status changes from 0 to 1
if (oldSnap.status === 0 && newSnap.status === 1) {
// do something
}
})
The one downside I can think of is I would have to do another read to get the device push token of the other user. Also, for every rental document update this cloud function will trigger, and ultimately may not even need to execute in the first place
The other way would be to have a notifications collection that stores notifications, and have a cloud function that triggers when a new notification document is added. Then, on the client side, when the user taps a button, update the status in the rental as well as create a new notification document.
Firestore.instance
.collection('rentals')
.document(rentalId)
.updateData({'status': newStatus});
Firestore.instance.collection('notifications').add({
'title': title,
'body': body,
'pushToken': <TOKEN HERE>,
});
In comparison to method 1, this does an extra write instead of a read.
Which method is better?
Both approaches can technically work and are valid. Which one you choose is depending on the use-case, and (given that both can work here) on personal preference. That's why I'll simply highlight a few key differences below, and explain when I personally choose to use which one.
The first approach you describe is treating your database like a state machine, where each state and state transition has specific meaning. You then use Cloud Functions to trigger code in the state transition.
The second approach treats the database as a queue, where the presence of data indicates what needs to happen. So Cloud Functions then triggers on the simple presence of the document.
I typically use a queue based approach for production work, since it makes it very easy to see how much work is left to be done. Anything in your notifications collection is a notification that needs to be sent.
In the state-transition data model it is much harder to see this information easily. In fact, you'll need to add extra fields to the document in order to be able to get this list of "pending notifications". For example: rentals with a pending notification are rentals where the timestamp that the status changed from 0 to 1 (a field you'll need to add, e.g. status_1_timestamp) is smaller than the timestamp the last notification was sent (a field like notification_timestamp).
But I sometimes use the state transition approach too. Usually when I want to transform the existing document, or because it's just a cool use-case to show (as in most cases the Firebase/Firestore SDKs would not expose both the old and new state).
I'd probably pick the queue based approach here, but as said before: that's a personal preference for me based on the reasoning above. If those reasons don't apply to you, or you have different reasons, that can be fine too.

A way to mark notification as seen without reading the notification feed?

My situation is the following:
I'm using the stream-js library. I add entries to the notification feeds of users for certain events - comments, follows, etc. After I write to their feed I also send a push notification to that user's device.
If a user clicks on a push notification I want to be able to mark the corresponding activity as seen. There's currently no way to do that since the add or addToMany calls do not return the ids of the added activities for me to send in the notification payload.
Ideally I'd want a way to mark a notification feed item as seen either by an activity group id or by some other unique id (or the foreignId). Is there a way to do that? If not, what is the alternative?
Two parts to this answer:
Getting the ID of an activity that you just added
The addActivity call in the various Stream client libraries (I'm using stream-js in this case) will return back the created activity, which should include the activity ID. Response looks something like this:
{
actor: 'ken',
duration: '9.65ms',
foreign_id: '',
id: '8b5d69a9-8b73-11e8-98ab-12cb9e7b86a4',
object: 'some-object',
origin: null,
target: '',
time: '2018-07-19T16:48:21.045496',
verb: 'add-activity'
}
Marking notification feed items as seen or read
The way to mark a notification feed item as seen or read is a little funky - first, you get the feed, like you would normally do, but you'll also pass in the mark_seen or mark_read options. (true will mark all items as seen or read, and an array of activity group IDs will mark only those items.)
From that call, the notification feed will be returned without the items marked as seen or read - but the next call to retrieve the notification feed will have the items marked accordingly.
More docs on that here: https://getstream.io/docs/flat_feeds/#notification_feeds
activity ID --> activity group ID
You might have noticed that you get the activity ID when adding the activity, but you need to pass in the activity group ID when marking items seen or read.
All notification feeds are actually aggregated feeds as well - by default, the aggregation format that they use is just the activity ID, which means that there will be only one activity per activity group, and the activity group ID will be the same as the activity ID. So, you can just use the activity ID returned by the addActivity call to get the notification feed and mark that activity group as seen or read.
If you're not using the default aggregation format (e.g., the activity group ID is not the same as the activity ID), then you'll likely have to retrieve the notification feed and grab the necessary activity group ID from there.

Related objects in activity feed

I'm building an activity feed application, where a user can like/comments on each activity feed. I went through GetStream.io documentation and looks like I'll have to send the activity with object ids.
{
id:"ef696c12-69ab-11e4-8080-80003644b625",
actor:"User:1",
object:"Comment:12",
started_at:"2014-11-11T15:06:16+01:00",
target:"Feed:100",
time:"2014-11-11T14:06:30.494",
verb:"add"
}
User:1 and Feed:12 are the objects in my application database? Does it mean that, while retrieving activities, I'll have to hit my database to retrieve the complete feeds?
Say the Feed:12 had few likes and comments earlier from other users. How do I get the complete set of likes/comments on user timeline feed?
What if I want to customize the view, say I want to show all users (image, name, the profile like etc) along with comment with timestamp similar to FB? Do I need to send these attributes as additional parameters for each feed?
Thanks,
Yes, when you fetch a feed from Stream and we give you back these references like user:1 or comment:12, we expect that you'd "enrich" those details from your database.
Typically what our users do is track the name of the model (eg, user) and the user_id (eg, 1). When you get the feed and put it into a hash map, you'll iterate over the activities, pull out all of the actor attributes, and do a single lookup like select * from user where id in (1,3,5,6,9,12) so that you're only hitting your database one time for all user objects or all comment objects or whatever. Then, replace those activities in your hash map so now you'd have actor: <object for User 9> and any other attributes you'd need for your UI presentment.
Then do the same for other references you pass in the activity, and so on.
Things we DON'T recommend are putting in string references for things that could change on your side. For example, if you had actor: "user:ian" instead of my user_id, if I ever change my username later then things probably wouldn't work properly on your side.

Resources