Enrichment

Last Edit: Feb 02 2020

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

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)

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:


{
    actor: 'User:1',
    verb: 'pin', 
    object: 'Place:42'
};
                    

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


{
    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


// 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')
});
                    

// 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)
});
                    

# https://github.com/GetStream/stream-django
from stream_django.enrich import Enrich
enricher = Enrich()
enriched_activities = enricher.enrich_activities(activities)
                    

# https://github.com/GetStream/stream-rails
enricher = StreamRails::Enrich.new
enriched_activities = enricher.enrich_activities(activities)
                    

# 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

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:


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 userFeed.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 userFeed.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 userFeed.get({ enrich: true });
console.log(data);
                    


                    

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"})
                    

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();
                    

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"})
                    

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)
                    }
                }
            }
        }
    }
}
                    

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.