.Net / C#

Enrichment

LAST EDIT Mar 19 2021

One essential best-practice when integrating with Stream is to keep your data normalized and use references to objects instead of full objects inside activities. The What To Store section covers this topic.

In most cases, it is better to store a reference inside your activities instead of an entire object. Doing so will make it easier to update data, and it will avoid complex sync problems. Because of this, a correct integration with Stream stores references to objects when adding activities and replaces them with full objects when reading the feeds. For example: if your activities have users as actors, your application will store the user ID in the actor field and replace the ID value with the user object before rendering the feed.

The process of replacing objects to refs and refs to objects is called "Enrichment" and can be done in two different ways.

Enrichment Using Stream Collection and User Endpoints

Copied!

The simplest way to organize your data is to store objects and users within Stream using collections and the user's API endpoints. You can then embed them inside your activities. With this method, you don't have to implement any server-side logic to convert objects into references and references back into objects. Activities read from the APIs will be fully enriched with your data.

Another advantage is that you can now read feeds and add activities directly from your frontend or mobile app.

Enrichment Using Your Own Database (Backend)

Copied!

If you only talk to Stream from the backend and you must keep data on your servers, the best approach is to use your own database for the enrichment process. Both the APIs and the API clients will make this very easy.

To clarify, we want to translate this:

1
2
3
4
5
{ 
    actor: 'User:1', 
    verb: 'pin',  
    object: 'Place:42' 
};

Into this (UserObject, PlaceObject and BoardObject are the objects stored in your database) :

1
2
3
4
5
{ 
    actor: UserObject, 
    verb: 'pin',  
    object: PlaceObject 
};

Stream provides enrichment out-of-the-box for some of the most popular ORMs. Please find below the examples for how to enrich activities using different combinations of frameworks/ORMs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// Using Waterline 
// https://github.com/GetStream/stream-node-orm 
const streamNode = require('getstream-node'); 
const streamWaterline = new streamNode.WaterlineBackend(); 
 
streamWaterline.enrichActivities(activities).then(function(enrichedActivities) { 
    res.json({'results': enrichedActivities}) 
}).catch(function(err) { 
    sails.log.error('enrichment failed', err) 
    return res.serverError('failed to load articles in the feed') 
}); 
 
// Using Mongoose 
// https://github.com/GetStream/stream-node-orm 
const streamNode = require('getstream-node'); 
const streamMongoose = new streamNode.MongooseBackend(); 
 
streamMongoose.enrichActivities(activities).then(function(enrichedActivities) { 
    console.log(enrichedActivities) 
}).catch(function(err) { 
    console.log('error', err) 
});
1
2
3
4
# https://github.com/GetStream/stream-django 
from stream_django.enrich import Enrich 
enricher = Enrich() 
enriched_activities = enricher.enrich_activities(activities)
1
2
3
# https://github.com/GetStream/stream-rails 
enricher = StreamRails::Enrich.new 
enriched_activities = enricher.enrich_activities(activities)
1
2
3
4
5
# https://github.com/GetStream/stream-laravel 
use GetStream\StreamLaravel\Enrich; 
$enricher = new Enrich(); 
$activities = $feed->getActivities(0,25)['results']; 
$activities = $enricher->enrichActivities($activities);

Feel free to reach out if your favorite ORM or Framework is missing.

Collections & Users

Copied!

Stream allows you to store arbitrary data with the collections and users API endpoints. Data stored this way can also be embedded inside activities so that enrichment is done automatically on Stream's side.

Here are a few examples of how you can embed a user and an object inside an activity:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
const feed = client.feed("user", "jack"); 
await client.setUser({ name: 'Jack' }); 
 
const post = await client.collections.add("post", "42-ways-to-improve-your-feed", { text: "..." }); 
 
await feed.addActivity({ 
  actor: client.currentUser, 
  verb: "post", 
  object: post, 
}); 
 
// if we now read Jack's feed we will get the enriched data automatically 
const response = await feed.get({ enrich: true }); 
console.log(response); 
 
// you can also update Jack's post and get the new version automatically propagated to his feed and its followers 
await client.collections.update("post", "42-ways-to-improve-your-feed", { text:"new version of the post" }); 
 
// jack's feed now has the new version of the data 
const data = await feed.get({ enrich: true }); 
console.log(data);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
feed = client.feed("user", "jack"); 
 
user = client.users.add("jack") 
post = client.collections.add("post", { text: "..." }, "42-ways-to-improve-your-feed", "jack"); 
 
feed.addActivity({ 
  "actor": client.users.create_reference(user) 
  "verb": "post", 
  "object": client.collections.create_reference(entry=post), 
}) 
 
# if we now read Jack's feed we will get the enriched data automatically 
response = feed.get(enrich=True) 
print(response); 
 
# you can also update Jack's post and get the new version automatically propagated to his feed and its followers 
client.collections.update("post", "42-ways-to-improve-your-feed", { text:"new version of the post" }) 
 
# jack's feed now has the new version of the data 
data = feed.get(enrich=True) 
print(data)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
feed = client.feed("user", "jack") 
 
# make sure we have the user data on Stream 
user = client.users.add( 
    "jack",  
    :data => {:name => "Jack", :profile_picture => "https://goo.gl/XSLLTA"},  
    :get_or_create => true 
) 
 
# then create a post inside a collection 
item = client.collections.add("post", {:text => "..."}, :id => "42-ways-to-improve-your-feed") 
 
# add the activity with user and post objects references 
feed.add_activity({ 
    :actor => client.users.create_reference(user), 
    :verb => "post", 
    :object => client.collections.create_reference(item), 
}) 
 
# if we now read Jack's feed we will get automatically the enriched data 
feed.get(:enrich => true) 
 
# we can also update Jack's post and get the new version automatically propagated to his feed and its followers  
client.collections.update("items", item["id"], :data => {:text => "new version of the post"})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
FlatFeed feed = client.flatFeed("user", "jack") 
 
// make sure we have the user data on Stream 
User user = client.user("jack").getOrCreate(new Data() 
        .set("name", "Jack") 
        .set("profile_picture", "https://goo.gl/XSLLTA")); 
 
// then create a post inside a collection 
CollectionData item = client.collections().add("post", new CollectionData("42-ways-to-improve-your-feed") 
        .set("text", "...")).join(); 
 
// add the activity with user and post objects references 
userFeed.addActivity(Activity.builder() 
        .actor(createUserReference(user.getID())) 
        .verb("post") 
        .object(createCollectionReference(item.getCollection(), item.getID())) 
        .build()).join(); 
 
// if we now read Jack's feed we will get automatically the enriched data 
feed.getEnrichedActivities(); 
 
// we can also update Jack's post and get the new version automatically propagated to his feed and its followers 
client.collections().update(item.getCollection(), item.set("text", "new version of the post")).join();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
feed := client.FlatFeed("user", "jack") 
 
// make sure we have the user data on Stream 
userData := stream.User{ 
	ID: "jack", 
	Data: map[string]interface{}{ 
		"name":            "Jack", 
		"profile_picture": "https://goo.gl/XSLLTA", 
	}, 
} 
user, err := client.Users().Add(userData, true) 
 
// then create a post inside a collection 
collectionData := stream.CollectionObject{ 
	ID: "42-ways-to-improve-your-feed", 
	Data: map[string]interface{}{ 
		"text": "...", 
	}, 
} 
item, err := client.Collections().Add("post", collectionData) 
 
// add the activity with user and post objects references 
activity := stream.Activity{ 
	Actor:  client.Users().CreateReference(user.ID), 
	Verb:   "post", 
	Object: client.Collections().CreateReference("post", item.ID), 
} 
feed.AddActivity(activity) 
 
// if we now read Jack's feed we will get automatically the enriched data 
enrichedResponse, err := feed.GetEnrichedActivities() 
 
// we can also update Jack's post and get the new version automatically propagated to his feed and its followers 
client.Collections().Update("items", item.ID, map[string]interface{}{"text": "new version of the post"})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
let userFeed = client.flatFeed(feedSlug: "user", userId: "jack") 
 
// setup an enriched activity type with the `Post` as the subclass of `CollectionObject` 
typealias UserPostActivity = EnrichedActivity<User, Post, String> 
 
client.create(user: User(id: "jack")) { result in 
    client.currentUser = try! result.get() 
     
    client.add(collectionObject: Post(text: "...", id: "42-ways-to-improve-your-feed")) { _ in 
        let post = try! result.get() 
        userFeed.add(UserPostActivity(actor: client.currentUser!, verb: "post", object: post)) { _ in 
            // if we now read Jack's feed we will get automatically the enriched data 
            userFeed.get(typeOf: UserPostActivity.self) { result in  
                print(result) 
                 
                // we can also update Jack's post and get the new version  
                // automatically propagated to his feed and its followers 
                post.text = "new version of the post" 
                client.update(collectionObject: post) { _ in 
                    userFeed.get(typeOf: UserPostActivity.self) { result in  
                        // jack's feed now has the new version of the data 
                        print(result) 
                    } 
                } 
            } 
        } 
    } 
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
const feed = client.Feed("user", "jack"); 
 
// make sure we have the user data on Stream 
const userData = new Dictionary<string, object>() 
{ 
        { "name", "Jack"}, 
        {"profile_picture", "https://goo.gl/XSLLTA"} 
}; 
const user = await client.Users.Add("jack", userData, true); 
 
// then create a post inside a collection 
const collectionData = new GenericData(); 
collectionData.SetData("text", "..."); 
const item = await client.Collections.Add("post", collectionData, "42-ways-to-improve-your-feed"); 
 
// add the activity with user and post objects references 
const activity = new Activity(user.Ref(), "post", item.Ref("post")); 
await feed.AddActivity(activity); 
 
// if we now read Jack's feed we will get automatically the enriched data 
await feed.GetEnrichedFlatActivities(); 
 
// we can also update Jack's post and get the new version automatically propagated to his feed and its followers client.collections.update("items", item["id"], data={text:"new version of the post"})

More on how to work with references to collections and users can be found here.