Breaking scary features down into tiny models and simple logic
If there's one thing developers can collectively agree on, it's that we have all, at one point in time, been faced with a feature we wanted to implement, but did not quite know how to go about implementing it. In this article, I'm going to show you how to approach such features using a recent project of mine (called Chatter) as an example. Chatter is a real-time messaging platform built using React and Django. This application offers a variety of features, some of which were less trivial to implement than the others. For the sake of simplicity, I will pick one that is easy to follow, does not require vast knowledge of a specific stack, and is transferrable to whatever feature you might want to implement, no matter how complicated. You would probably have noticed how in messaging platforms like WhatsApp, each member of a group chat is assigned a color to their display name. The purpose of this feature is to help customers track who said what, by creating a visual distinction between users. To implement this feature, we will observe the following steps: 1. Define the feature Without diving into any technical details whatsoever, what is this feature we're trying to implement? In our case, we want users in a group chat to have different colors assigned to their names. Whenever Mark sends a message to a group chat, his name appears, say yellow- and when Laura does, her name appears green. 2. Give it a little more thought Now that we have a basic understanding of how the feature should work, it's time to go a little deeper. Should Mark's name appear yellow in all group chats, or just some? Does he pick the color, or is it selected at random? Does the color ever change, or should it remain that way as long as he's a member of the group chat? In this step, it is crucial to attempt to ask all the necessary questions surrounding your feature, as they will play the biggest role in determining the success of its implementation. 3. Getting technical Most features in web applications work by processing and storing data in one way or the other. In this step, we will describe what data (models) will be worked with, and how (logic). 4. Check your step (3) against steps (1) and (2) Do the models you defined capture all the data necessary for implementing your feature? Did you happen to forget one tiny detail? What about the logic, does it make sense on paper? Do you have the tools and knowledge about how to make it work and with what techniques? If you are unsure about the answers to any of these questions, it is perfectly normal (and actually recommended) to retrace your steps and identify what you may have let slip, before proceeding. If you eventually do choose to proceed and later discover a flaw in your logic or models, it's not the end of the world. You will surely get better at these things with time. Address the oversight you noticed, then reiterate. The more complicated your feature is, the less likely it is that you get it working in just one go. It is not uncommon to get stuck while ironing out technical details, so do not feel alone. 5. Fire your engines It's time to start implementing! The following segment will briefly illustrate how I walked through each of these steps and ended up with one less feature on my checklist. It will also show fragments of code pulled directly from the project's folder(s). Defining the feature I want each user in a group chat to see the names of other users displayed in different colors. Giving it a little more thought I want Mark's color to be chosen at random whenever he is added to a group. This means that while he appears as color yellow in group chat "A", he may appear as color blue in group chat "B". For more flexibility and personalization, I want Laura's view of Mark in group chat "A" to be different from Percy's view of him in the same group chat "A". This means that he could appear as yellow to Laura in group chat "A", but appear as purple to Percy in that very same group chat. Getting technical Here's the flow I'm trying to achieve: A user/admin initializes a group chat with a certain set of members. Each of these members (including the creator) is assigned colors which they will see other members' names displayed in. That is: User A will see -> User B as Green, User C as Pink, User D as Cyan. User B will see -> User A as Purple, User C as Grey, User D as Orange. ...and so on, in that particular group chat. If a user, User Z, wasn't part of the members the group was initialized with, and an admin decides to add him in future, then User Z will be assigned colors which he will see other members' names displayed in and vice-versa. That is: User Z will see -> User A as Violet, User B as Yellow... and so on. And then: User A will see -> User Z as Blue User B will see -> User Z as Pink ...and so on, in that particular group chat. That's about it for the logic. Now for the mod
If there's one thing developers can collectively agree on, it's that we have all, at one point in time, been faced with a feature we wanted to implement, but did not quite know how to go about implementing it.
In this article, I'm going to show you how to approach such features using a recent project of mine (called Chatter) as an example.
Chatter is a real-time messaging platform built using React and Django. This application offers a variety of features, some of which were less trivial to implement than the others. For the sake of simplicity, I will pick one that is easy to follow, does not require vast knowledge of a specific stack, and is transferrable to whatever feature you might want to implement, no matter how complicated.
You would probably have noticed how in messaging platforms like WhatsApp, each member of a group chat is assigned a color to their display name. The purpose of this feature is to help customers track who said what, by creating a visual distinction between users.
To implement this feature, we will observe the following steps:
1. Define the feature
Without diving into any technical details whatsoever, what is this feature we're trying to implement? In our case, we want users in a group chat to have different colors assigned to their names. Whenever Mark sends a message to a group chat, his name appears, say yellow- and when Laura does, her name appears green.
2. Give it a little more thought
Now that we have a basic understanding of how the feature should work, it's time to go a little deeper. Should Mark's name appear yellow in all group chats, or just some? Does he pick the color, or is it selected at random? Does the color ever change, or should it remain that way as long as he's a member of the group chat?
In this step, it is crucial to attempt to ask all the necessary questions surrounding your feature, as they will play the biggest role in determining the success of its implementation.
3. Getting technical
Most features in web applications work by processing and storing data in one way or the other. In this step, we will describe what data (models) will be worked with, and how (logic).
4. Check your step (3) against steps (1) and (2)
Do the models you defined capture all the data necessary for implementing your feature? Did you happen to forget one tiny detail? What about the logic, does it make sense on paper? Do you have the tools and knowledge about how to make it work and with what techniques?
If you are unsure about the answers to any of these questions, it is perfectly normal (and actually recommended) to retrace your steps and identify what you may have let slip, before proceeding.
If you eventually do choose to proceed and later discover a flaw in your logic or models, it's not the end of the world. You will surely get better at these things with time. Address the oversight you noticed, then reiterate.
The more complicated your feature is, the less likely it is that you get it working in just one go. It is not uncommon to get stuck while ironing out technical details, so do not feel alone.
5. Fire your engines
It's time to start implementing!
The following segment will briefly illustrate how I walked through each of these steps and ended up with one less feature on my checklist. It will also show fragments of code pulled directly from the project's folder(s).
Defining the feature
I want each user in a group chat to see the names of other users displayed in different colors.
Giving it a little more thought
I want Mark's color to be chosen at random whenever he is added to a group. This means that while he appears as color yellow in group chat "A", he may appear as color blue in group chat "B".
For more flexibility and personalization, I want Laura's view of Mark in group chat "A" to be different from Percy's view of him in the same group chat "A". This means that he could appear as yellow to Laura in group chat "A", but appear as purple to Percy in that very same group chat.
Getting technical
Here's the flow I'm trying to achieve:
A user/admin initializes a group chat with a certain set of members.
-
Each of these members (including the creator) is assigned colors which they will see other members' names displayed in. That is:
- User A will see -> User B as Green, User C as Pink, User D as Cyan.
- User B will see -> User A as Purple, User C as Grey, User D as Orange.
- ...and so on, in that particular group chat.
-
If a user, User Z, wasn't part of the members the group was initialized with, and an admin decides to add him in future, then User Z will be assigned colors which he will see other members' names displayed in and vice-versa. That is:
- User Z will see -> User A as Violet, User B as Yellow... and so on. And then:
- User A will see -> User Z as Blue
- User B will see -> User Z as Pink
- ...and so on, in that particular group chat.
That's about it for the logic. Now for the models.
To associate each user in a group chat with a color, we would, at the very least, require User
and GroupChat
models. At this point of the project, we're going to assume that we already have those in place, so most of our focus will be on a new model which I will call UserGroupContactColorMap
. This model will store the color value (in hex) to associate with every other member of the group.
One more check
My models seem adequate for what I'm trying to achieve, and the logic pretty much makes sense to me, so I move to the next and final step.
Time to implement
Enough talk. It's time to do some actual coding.
This is the User
model:
class CustomUser(AbstractBaseUser, PermissionsMixin):
class OnlineStatus(models.TextChoices):
ONLINE = 'ONLINE', 'Online'
OFFLINE = 'OFFLINE', 'Offline'
email = models.EmailField(_("email address"), unique=True)
profile_picture = models.ImageField(
blank=True,
null=True,
upload_to='profile_pictures/',
validators=[validate_profile_picture]
)
first_name = models.CharField(max_length=140)
last_name = models.CharField(max_length=140)
display_name = models.CharField(max_length=160, blank=True, null=True)
status = models.CharField(max_length=10, choices=OnlineStatus.choices, default=OnlineStatus.OFFLINE)
...
...
...
# Other user-specific fields and metadata
This is the GroupChat
model:
class GroupChat(models.Model):
title = models.CharField(max_length=100)
owner = models.ForeignKey('accounts.CustomUser', on_delete=models.CASCADE, related_name='groupchats_owned')
admins = models.ManyToManyField('accounts.CustomUser', related_name='groupchats_admninistrated')
description = models.TextField()
picture = models.ImageField(
blank=True,
null=True,
upload_to='group_chat_display_pictures/',
validators=[validate_profile_picture]
)
members = models.ManyToManyField('accounts.CustomUser')
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
This is the UserGroupContactColorMap
model:
class UserGroupContactColorMap(models.Model):
user = models.ForeignKey('accounts.CustomUser', on_delete=models.CASCADE, related_name='user_group_contact_color_maps')
group = models.ForeignKey('chats.GroupChat', on_delete=models.CASCADE)
contact_user = models.ForeignKey('accounts.CustomUser', on_delete=models.CASCADE)
color = models.CharField(max_length=8, default=get_random_color)
To store a random color value in each UserGroupContactColorMap
instance, I make use of a helper function called get_random_color()
. This function will automatically take care of assigning the color value because it is set as its default:
filtered_colors = [
"#FF5733", # Fiery Orange
"#33FF57", # Bright Green
"#3357FF", # Vivid Blue
"#FF33A8", # Vibrant Pink
"#800080", # Purple
"#FF4500", # Orange Red
"#2E8B57", # Sea Green
"#8A2BE2", # Blue Violet
...
...
...
# and some more fun colors.
]
def get_random_color():
return random.choice(filtered_colors)
In my endpoint for group chat creation, after the GroupChat
instance is created, UserGroupContactColorMap
instances are created for each member of the group:
# Take each member of the group and create color-maps for every other member
for user_member in new_group_chat.members.all():
for other_member in new_group_chat.members.exclude(
id=user_member.id
):
UserGroupContactColorMap.objects.create(
user=user_member,
group=new_group_chat,
contact_user=other_member
)
serializer = GroupChatDisplaySerializer(
new_group_chat,
context={"user_id": user.id}
)
return Response(serializer.data)
Each user client is presented a contact color map (alongside some other data necessary for rendering a group chat to the browser) that maps the IDs of every other member of the group to the colors randomly assigned to them on group creation:
"contact_color_map": {
"72": "#6495ED",
"73": "#32CD32",
"75": "#4682B4",
"76": "#EE82EE",
"82": "#FF33A8"
}
Then the messages are rendered like such:
{groupChat?.messages.length > 0 ?
groupChat?.messages.map((message, idx) => (
)) : (
Send a message to start the conversation.
)}
A closer look at the GroupInboxMessage
component:
export default function GroupInboxMessage({ message, userIsSender, displayNameColor}){
if(!userIsSender){
return (
{message?.sender?.name.split(' ')[0]}
{formatDatetime(message?.created_at)}
{message?.content}
)}else{
return (
{formatDatetime(message?.created_at)}
{message.content}
)
}
}
Aaand Voila! Here's the final look:
Sometimes the feature we want to implement is not even all that complex. Thinking of it only as a whole rather than carefully breaking it down into calculated steps is what tends to give it that image.
As you become more experienced, you will begin to consider things like performance and whether your logic is efficient regarding usage of resources. For example, how can my logic for creating color maps be rewritten to optimize performance and reduce database hits? What if the platform has hundreds of users in each group chat? Would bulk-creation and batch-processing do the trick? I personally would regard such considerations in an additional step (6).
If you're already aware of methods to ensure the efficiency of the code for your feature, go right ahead and use them; performance is a very important metric for code. Otherwise, I wouldn't advise you to spend hours or days trying to make your code 100% efficient in one go before actually completing the implementation. I'd say give it your best shot, then come back to optimize as soon as you can.
I hope this framework helps you conquer whatever feature needs arise in your project(s).