Database Architecture for "Badge" System & Arbitrary Criteria (MySQL/PHP)
Quickie-Question:
To summarize, I'm a little confused as to how I would design such a database that allows indefinite badge-rule creation without requiring structural changes to the previously existing user-tables in the database.
Storing Badge Title, Criteria, etc. What would that Table look like?
- badge_id (1)
- badge_title (10K Badge)
- badge_image (10k.jpg)
- badge_criteria ([posts] >= 10000)
...
Winded-Question:
I would like to implement a badge-system on my own personal projects, but am seeking a bit of advice as to how one would best do such a thing. I've been reading some of the questions here about badge-systems, but don't see the database-architecture getting much attention.
Badges that are based on user-points (Hypothetical "10k Badge") would seem pretty straight forward. Any event that affects the users reputation (upvotes, downvotes, answer-accepted, etc) would invoke a method to review the users new reputation, and potentially award a badge.
That system sounds pretty straight forward, but what does that look like as a database for the administrator who wants to create innumerable amounts of badges with little-effort down the road - some of which may be based on different criteria, and not simply the user reputation.
User-reputation is likely a value within the user-record itself. But ideally, wouldn't you want to avoid having to add new fields to the user table when you create new badges? For instance the "Edited 100 Entries" badge - you wouldn't create a new column "entries_edited" within the Users Table, would you? And then increment that after each entry-edited...
Any hints?
Stackoverflow Archive:
- Best way to Store Badge Criteria (Not a duplicate)
- Designing a Badge System: Where to fire business-logic?
Note: I'm NOT asking how to associate badges with users. I'm NOT asking how to award badges (that will be done programmatically)
Solution 1:
Given that the badge criteria can be arbitrarily complex, I don't think you can store it in a database table broken down into "simple" data elements. Trying to write a "rules engine" that can handle arbitrarily complex criteria is going to take you down the path of basically re-writing all the tools that you have in your programming language.
If you know in advance you want the badges limited to only certain fields (i.e. badges are only based off reputation or number of edits or something), then you can store those in a simple table like:
ReputationBadgeCriteria
BadgeId
BadgeName
MinReputation
Alternatively, you could use some kind of DSL to write your "rules" but you end up having to also create a parser to parse the rules when you read them as well as something to execute these rules. Depending on the complexity you want in your DSL, this may not be a trivial task. This looks like the path you are going in your question with having a Criteria column (presumably plain text) that has something like "[Reputation] > 1000" or "[Posts] > 5" in it. You still have to parse and execute those rules and the complexity of writing something to do so is dependent on how complex you want those rules to be.
I would recommend you read these Daily WTF articles for information on why this approach leads to pain.
Solution 2:
Depending on how far you want to go with it, your schema can get pretty complicated. It seems to me that the base elements you need to track are:
Badges awarded
Points earned
Pretty simple so far, but you want to be able to dynamically create new badges and new points categories. Badge awards would depend on earning points in one or more point categories that would add up to a certain amount. So you need to track the relationship between point categories (and points earned) and badges:
Point categories
Badge categories
So the key would be your user points table, which would link to point categories, which link to badges. Users earn points in a particular category, which would contribute to earning points towards one or more badges.
badges:
badge_id
badge_name
required_points
....
point_categories:
point_id
category_name
weighting (optional)
...
point_groups:
badge_id
point_id
weighting (optional)
...
user_points:
user_id
point_id
points
...
user_badges:
user_id
badge_id
points_earned
badge_awarded (yes/no)
...
Your "admin" interface would allow someone to create a new badge and pick which point categories are required to earn that badge (point_groups). Whenever a user earns points (user_points), you update the user_points table, then determine which badges those points would could contribute to (point_groups). You then recompile the points for the badges that were affected by the points earned and update the user_badges table with the point_earned. Then check the points_earned field in user_badges against the required_points in the badges table.
You can get much fancier by assigning different weights to different point categories, or even different weights for point categories for particular badges. But this setup would allow an unlimited amount of badges and point categories to be created and managed fairly easily without changing tables structures.
If that is completely not what you are looking for, then I think I should at least get a vote or two for a lot of typing.