Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Enriching Mobile Apps With the Social Networking Experience Using Go and Redis

DZone's Guide to

Enriching Mobile Apps With the Social Networking Experience Using Go and Redis

Learn how the social media events of following someone and liking a post or product can be described with these simple Redis commands you can implement in your app.

· Mobile Zone
Free Resource

Download this comprehensive Mobile Testing Reference Guide to help prioritize which mobile devices and OSs to test against, brought to you in partnership with Sauce Labs.

Every mobile app these days is using social network integration to make the application more unique and attractive for a range of different users. The social network experience and gamification are two very important parts of every mobile app. It doesn‘t matter in which category the application belongs, every mobile app is using some social integration. Making the application more popular and more attractive not only requires a beautiful user interface (UI), but also other conditions like user experience (UX) and user interaction within the application.

Social networking enriches the UX, which keeps the users busy interacting with each other in the application, making them stay longer and use it more actively as part of their life. In other words, the social network experience makes users more addicted to the application. Social network interaction also affects easily attracting and targeting specific user groups and makes the application organically grow, which is very important for every business model.

But implementing a social experience in the application could be a very interesting and challenging job because of so many social services on the web providing the same or similar options, and obstacles for implementation, like services which provide an API for setting a photo or product as a favorite, liking or rating the photo/product, sharing the photo/product with friends/followers, recommendations based on some criteria or geolocation, etc.

It's very important to notice that for most of the services, a proprietary SDK is required, which makes our app vulnerable to leaking the data and using it for different purposes.

In this tutorial, we will see how easy it is to implement a personal social service and use it to make a mobile application unique and more attractive for the end users. Our goal is to implement a standalone RESTful microservice which will provide some commonly used social functionalities like favorites, followers, likes, and rates.

Because of the simplicity and performance, we’ll implement our microservice in the Go lang using Redis NoSQL as a data store.

The required basic endpoints for the following and favoriting social events are

  • /set_relation – endpoint required for setting/unsetting the follow relation.
  • /list_following – endpoint required for listing users which user X is following.
  • /list_followed – endpoint required for listing users that follow user X.
  • /list_connections_stat  – endpoint required for listing simple info: how many users are following the user X and how many users user X is following.
  • /add_favorite – add a favorite for user X.
  • /remove_favorite  – remove favorites from user X.
  • /list_favorites  – list user X favorites.

This microservice uses two databases: MySQL RDBMS, the most popular open source RDBMS, and Redis NoSQL, the most popular data structure NoSQL.

Why two databases when everything can be stored at only one?

Suppose we have an application which is used daily by thousands of concurrent users, which requires fast access to the user social information and must fast calculate statistics in real time; using only RDBMS is not enough. In RDBMS, we’ll store data which is not often modified, like user profile, image info, video info, news, statuses, etc. Following the best practices of popular social networks like Facebook, Instagram, and many other companies who make real-time statistics, NoSQL DB is required because of easy horizontal scaling and fast real-time access to specific information used in different cases. In our case, Redis NoSQL is selected as the most adequate DB for storing dynamic data like user relations, indexes for favorites, number of visits, storing social likes, and storing and calculating social rates, and also for basic analytics.

Because of the many Redis client libraries for Go, the decision for the client lib is left to the developer. In this tutorial, we use "github.com/go-redis/redis" but first, we will use only Redis commands to explain the logic, and later we’ll see only the specific pieces of code for how this is implemented in Go.

Following Events

A following event is a very popular social event implemented in every social network, which describes the relationship between two users. For example:

Scenario 1:

User A is following User B, User A → User B.

Image title

Scenario 2:

User A is followed by User C,  User C → User A.

User A is following User C,  User A → User C.

Image title

In this short example, we see two scenarios. In the first one, the relationship is only in one-way, in a single direction, and the second one goes in both ways (bi-directional relationship).

In a simple social network in the first relation, when the User B trigger some social event- for example, uploading a new photo- User A will be notified of that event, but when User A triggers some social event, this event will not notify User B because User B is not following User A. In the second scenario, we have a bi-directional relationship where User C is following User A, but also, User A is following User C, which means that when one of them triggers a social event, the other will receive a notification. It doesn’t matter which one, User A or User C, because they are both in the relationship.

How is this implemented with simple Redis commands?

We will use the Redis Set data structure for keeping user IDs which are in the relationship, and a simple incremental Key for getting information about the number of relationships for a specific user instead of counting the Set structure.

In our example, we will describe a one-way relationship where User A is ID: 1111111111 and User B is ID: 2222222222.

SADD user:1111111111:following 2222222222

SADD user:2222222222:followed 1111111111
INCR user:1111111111:followingNo

INCR user:2222222222:followedNo

So for a bi-directional relationship, we will duplicate SETs and KEYs, but a bi-directional relationship can only happen when the second user decides to follow the first user.

Removing the relationship is also easy; using the Redis command SREM will remove a specific ID from the specific Set.

SREM user:1111111111:following 2222222222

SREM user:2222222222:following 1111111111

But also we need to decrease the user's counter with the command DECR.

DECR user:1111111111:followingNo

DECR user:2222222222:followedNo

With a simple call of the Redis command below, we can extract all user ids from the SET of the User A, which User A is following:

 SMEMBERS user:1111111111:following 

Or, for the extraction of the user ids which follow User A, we only change the last part of the key, because those user ids are stored in the other SET:

 SMEMBERS user:1111111111:followed 

If we want to check if some user is followed by some other one, or is following some other user, we can use a Redis command for checking if a value exists in the specific SET, SISMEMBER:

 SISMEMBER user:1111111111:following 2222222222  or SISMEMBER user:2222222222:followed 1111111111Implementation in Golang is straightforward, but we will see only the methods important for the routes, database connection, and setting the relationship.

main.go

func main() {
  socialApi := CreateApiHandlers()
        router := CreateNewRouter(socialApi)

        log.Fatal(http.ListenAndServe(":8080", router))
}

routes.go

func CreateNewRouter(handlers *Handlers) *httprouter.Router {

            router := httprouter.New()

            router.POST("/set_relation", handlers.uController.setUserRelations)
            router.POST("/list_following", handlers.uController.listAllFollowingConnections)
            router.POST("/list_followed", handlers.uController.listAllFollowedConnections)
            router.POST("/list_connections_stat", handlers.uController.listConnectionsStats)
            router.POST("/add_favorite", handlers.uController.addToFavorites)
            router.POST("/remove_favorite", handlers.uController.removeFromFavorites)
            router.POST("/list_favorites", handlers.uController.listFavorites)

            return router

}

handlers.go

type Handlers struct {
            dbConnection *DBConnection
            uController  *UsersController
}

func CreateApiHandlers() *Handlers {
            API := &Handlers{
                        dbConnection: OpenConnectionSession(),
                        uController:  &UsersController{},
            }
            API.uController.dbConnection = API.dbConnection

            return API
}

dbconnection.go

type DBConnection struct {
            db    *sql.DB
            cache *redis.Client
}

func OpenConnectionSession() (dbConnection *DBConnection) {
            dbConnection = new(DBConnection)
            dbConnection.createNewDBConnection()
            dbConnection.createNewCacheConnection()

            return
}

userscontroller.go

type UsersController struct {
            dbConnection *DBConnection
}

func (uController *UsersController) setUserRelations(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
            userIDSource := r.FormValue("user_id_source")
            userIDTarget := r.FormValue("user_id_target")
            relationType := r.FormValue("relation_type")

            redisDB := uController.dbConnection.cache

            if relationType == "FOLLOW" {
                        redisDB.SAdd("usr:"+userIDSource+":following", userIDTarget)
                        redisDB.SAdd("usr:"+userIDTarget+":followed", userIDSource)
                        redisDB.Incr("usr:" + userIDSource + ":followingNo")
                        redisDB.Incr("usr:" + userIDTarget + ":followedNo")
            } else if relationType == "UNFOLLOW" {
                        redisDB.SRem("usr:"+userIDSource+":following", userIDTarget)
                        redisDB.SRem("usr:"+userIDTarget+":followed", userIDSource)
                        redisDB.Decr("usr:" + userIDSource + ":followingNo")
                        redisDB.Decr("usr:" + userIDTarget + ":followedNo")
            }

            w.Header().Set("Content-Type", "application/json; -8")
            w.WriteHeader(http.StatusOK)
}

Compiling the source code is not enough for proper testing. Because we are using two databases, first we need to have them installed somewhere and then configured in the dbconnection.go. Also, we need to modify the simple SQL query in userscontroller.go to match existing DB schema.

The rest of the service is available on GitHub.

Analysts agree that a mix of emulators/simulators and real devices are necessary to optimize your mobile app testing - learn more in this white paper, brought to you in partnership with Sauce Labs.

Topics:
redis ,mysql ,restful api ,mobile ,mobile app development ,golang ,microservices

Published at DZone with permission of Zharko Popovski. See the original article here.

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}