views:

336

answers:

4

I've given some thought to implementing badges (just like the badges here on Stack Overflow) and think it would be difficult without Windows services, but I'd like to avoid that if possible.

I came up with a plan to implement some examples:

  • Audobiographer: Check if all fields in the profile is filled out.
  • Commentor: When making a comment check if the number of comments equal 10, if so award the badge.
  • Good Answer: When voting up check to see if vote score is 25 or higher.

How could this be implemented in the database? Or would another way be better?

+2  A: 

Jobs. That is the key. Out of process jobs that run at set intervals to check the criteria that you mention. I don't think you even need to have a windows service unless it requires some external resources to set the levels. I actually think StackOverflow uses jobs as well for their calculations.

spinon
I should also add that to avoid manually checking every user every time you would probably want to keep a table of activity so that you could have your list of people that you would want to check. You obviously don't want to waste resources processing users that haven't had any recent activity.
spinon
Do jobs run automatically? Where can I find more information on jobs?
Luke101
Jobs can be scheduled to run on the database. You build them and then set their interval for which they should execute. You will notice on SO that once you complete an action it doesn't necessarily reward you right away. So I would imagine they have things set to run every few minutes or so. But I really have no idea. But a little delay is not a big deal but I would wait till the end of the day for processing.
spinon
This would work, but I would argue putting this out-of-process adds unnecessary maintenance overhead long-term.
Rex M
A: 

You could use triggers and check upon update or insert, then if your conditions are met add badge. That would handle it pretty seem less. Commence the trigger bashing in 3, 2, 1...

Dustin Laine
This is exactly the kind of (valid) reason triggers are bashed.
Rex M
I agree there are many disadvantages of triggers, but IMHO they mostly revolve around not including business logic in a data layer/database. Is there any performance or validity issues that arise from triggers, if they are written and implemented properly?
Dustin Laine
@durilai performance and validity issues tend to arise from triggers because they are, relatively speaking, very difficult to manage. They are side-effect-oriented, whereas in general it's commonly accepted that code with side effects is a recipe for disaster. It's hard to make a strong case for a single operation (e.g. insert) to silently have - from the caller's perspective - an unknown amount of additional side-effects to the system.
Rex M
@Rex M - Thanks for the explanation. I agree with you on this, also nice answer!
Dustin Laine
A: 

comments must be stored within the database right? then i think there are two main ways to do this.

1) when a user logs in you get a count of the comments. this is obvisously not the desired approach as the count could take a lot of time

2) when a user posts a comment you could either do a count then and store the count with the use details or you could do a trigger which executes when a comment is added. the trigger would then get the details of the newly created comment, grab the user id, get a count and store that against the user in a table of some sort.

i like the idea of a trigger as your program can return w/out waiting for sql server to do its stuff.

griegs
+16  A: 

A similar-to-Stackoverflow implementation is actually a lot simpler than you have described, based on bits of info dropped by the team every once in awhile.

In the database, you simply store a collection of BadgeID-UserID pairs to track who has what (and a count or a rowID to allow multiple awards for some badges).

In the application, there is a worker object for each badge type. The object is in cache, and when the cache expires, the worker runs its own logic for determining who should get the badge and making the updates, and then it re-inserts itself into the cache:

public abstract class BadgeJob
{
    protected BadgeJob()
    {
        //start cycling on initialization
        Insert();
    }

    //override to provide specific badge logic
    protected abstract void AwardBadges();

    //how long to wait between iterations
    protected abstract TimeSpan Interval { get; }

    private void Callback(string key, object value, CacheItemRemovedReason reason)
    {
        if (reason == CacheItemRemovedReason.Expired)
        {
            this.AwardBadges();
            this.Insert();
        }
    }

    private void Insert()
    {
        HttpRuntime.Cache.Add(this.GetType().ToString(),
            this,
            null,
            Cache.NoAbsoluteExpiration,
            this.Interval,
            CacheItemPriority.Normal,
            this.Callback);
    }
}

And a concrete implementation:

public class CommenterBadge : BadgeJob
{
    public CommenterBadge() : base() { }

    protected override void AwardBadges()
    {
        //select all users who have more than x comments 
        //and dont have the commenter badge
        //add badges
    }

    //run every 10 minutes
    protected override TimeSpan Interval
    {
        get { return new TimeSpan(0,10,0); }
    }
}
Rex M
wow. This is a good idea. How do you know when the cache expires. Is there like a call back function you can call when the cache expires?
Luke101
@Luke101 see the code sample
Rex M
@Luke101: it's a feature of the .NET caching infrastructure: when the timeout expires (and the object is about to removed from the cache) the runtime calls your callback automatically.
Dean Harding
This is quite cool, but isn't there a more declarative/explicit way of running periodic jobs in the ASP.NET process? Also, what thread does this run on when it fires? Is this well suited to long-running processes, such as calculating badges that span thousands of database entries?
Drew Noakes
@Drew ASP.NET is not suited to anything other than, well, serving web requests. That means high turnover for a large number of short-lived threads. This approach is *easy*, but that's all. For very different needs, we have totally different application models - such as Windows Services.To answer the threading question - it runs on a thread the ASP.NET runtime specifically spins up to manage cache.
Rex M
I think this is a pretty good approach. But I wonder about two things; 1) What happens when you get a very large number of badges (jobs). Would this not use up a lot of worker threads from asp.net? To me, a windows service would seem like a good candidate for this 2) Recalculating each time is good because it means if you change logic you can re-calc badges. But as you begin to get a lot of records, wouldn't this get slower and slower?
Joshua Hayes
@Joshua it only uses one thread - the cache expiration is managed by one thread. Yes - it does get slower and slower. In fact, Stackoverflow's badge jobs run much less frequently than they did shortly after launch, likely for the same reason. So you are right - in any formal setting, a Windows Service definitely would be more robust and architecturally sound. The point of this approach is that it is easy and keeps everything in the same component.
Rex M