HiveBrain v1.2.0
Get Started
← Back to all entries
patterncsharpMinor

GitHub (and Bitbucket) Pushing Events

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
eventsgithubbitbucketandpushing

Problem

This code is for dispatching and handling certain events from GitHub (and also Bitbucket). Things like pushes, issues, etc. can be handled by this system, and strongly-typed objects will be returned.

I left the XML docs out of all these, as somewhere from 2/3 to 3/4 of the files were XML docs. (We all know how much I love my XML docs.)

The first file here is the IEventDispatcher which is what handles the event dispatching. This is a common interface between the GitHub and Bitbucket dispatchers. On GitHub: IEventDispatcher.

public interface IEventDispatcher
{
    T Deserialze(string json);

    void Dispatch(string eventKey, string json);
}


This next file is the GitHub dispatcher. On GitHub: GitHub.EventDispatcher.

```
public class EventDispatcher : IEventDispatcher
{
public void Dispatch(string eventKey, string json)
{
switch (eventKey)
{
case CommitCommentEvent.WebhookEventName:
OnCommitCommentReceived(new CommitCommentEventArgs(Deserialze(json)));
break;
case CreateEvent.WebhookEventName:
OnCreateReceived(new CreateEventArgs(Deserialze(json)));
break;
case DeleteEvent.WebhookEventName:
OnDeleteReceived(new DeleteEventArgs(Deserialze(json)));
break;
case DeploymentEvent.WebhookEventName:
OnDeploymentReceived(new DeploymentEventArgs(Deserialze(json)));
break;
case DeploymentStatusEvent.WebhookEventName:
OnDeploymentStatusReceived(new DeploymentStatusEventArgs(Deserialze(json)));
break;
case ForkEvent.WebhookEventName:
OnForkReceived(new ForkEventArgs(Deserialze(json)));
break;
case GollumEvent.WebhookEventName:
OnGollumReceived(new GollumEventArgs(Deserialze(json)));
break;
case IssueCommentEvent.WebhookEventN

Solution

Let us tackle this code top down.

public interface IEventDispatcher
{
    T Deserialze(string json);

    void Dispatch(string eventKey, string json);
}


This is nice and clean, but I have the feeling that the Deserialize() method is somehow misplaced in that interface.

A dispatcher should just have only the Dispatch() method because that is his job. I don't say that an implementation of this interface shouldn't have a Deserialize() method but that method doesn't need to be in the interface hence it should't be public.

public interface IEventDispatcher
{
    void Dispatch(string eventKey, string json);
}


Now assume someday jason isn't anymore that hip or will be replaced by some better format, the best would be to add an interface ISerializer of which an implementation will be constructor injected into the EventDispatcher, something along this lines

public interface ISerializer
{
    T Deserialze(string json);
}




public class JasonSerializer : ISerializer
{
    public T Deserialze(string json)
    {
        DataContractJsonSerializer serializer = new DataContractJsonSerializer(typeof(T));
        using (MemoryStream ms = new MemoryStream(Encoding.UTF8.GetBytes(json)))
        {
            return (T)serializer.ReadObject(ms);
        }
    }
}


by the way, setting the Position property to 0 isn't needed here, because after instantiation of the MemoryStream the Position is 0.

public void Dispatch(string eventKey, string json)

Assume the class would have a Dictionary> for each passed in eventKey like so

private readonly ISerializer serializer;
public EventDispatcher(ISerializer serializer)
{
    this.serializer = serializer;
    InitializeEventsDictionary();
}

private T Deserialze(string json)
{
    return serializer.Deserialze(json);
}

Private Dictionary> events = Dictionary>();
private void InitializeEventsDictionary()
{
    events.Add(CommitCommentEvent.WebhookEventName, (s) => { OnCommitCommentReceived(() => { return new CommitCommentEventArgs(Deserialze(s)); }); });
    events.Add(CreateEvent.WebhookEventName, (s) => { OnCreateReceived(() => { return new CreateEventArgs(Deserialze(s)); }); });
    ....
    ....
}


and if we change the signatures of the OnXXX methods like so

protected void OnCommitCommentReceived(Func e)
{
    var del = CommitCommentEventReceived;
    del?.Invoke(this, e.Invoke());
}


the Dispatch() method could look like this

public void Dispatch(string eventKey, string json)
{
    Action action;
    if (events.TryGetValue(eventKey, out action))
    {
        action.Invoke(jason);
    }
}


If this dispatcher is used by any kind of plugin you should ensure that an eventhandler which throws an exception does not break the whole applictation. This can be done by wraping the del?.Invoke(this, e.Invoke()); in a try..catch block, but if one of the plugins throws, the whole thing would go in the catch block and therefor any following eventhandler wouldn't get the event.

A solution to this would be to use the invocationlist of the event and iterate over the eventhandlers wrapped in a try..catch like so

protected void OnCommitCommentReceived(Func f)
{
    var del = CommitCommentEventReceived;
    if (del == null) {return;}

    CommitCommentEventArgs e = f.Invoke();
    foreach(EventHandler handler in del.GetInvocationList())
    {
        try
        {
            handler(this, e);
        }
        catch(Exception ex)
        {
            // because we don't/can't know which exception is thrown lets catch them all
            // and do some logging here if we don't want to swallow them
        }
    }
}

Code Snippets

public interface IEventDispatcher
{
    T Deserialze<T>(string json);

    void Dispatch(string eventKey, string json);
}
public interface IEventDispatcher
{
    void Dispatch(string eventKey, string json);
}
public interface ISerializer
{
    T Deserialze<T>(string json);
}
public class JasonSerializer : ISerializer
{
    public T Deserialze<T>(string json)
    {
        DataContractJsonSerializer serializer = new DataContractJsonSerializer(typeof(T));
        using (MemoryStream ms = new MemoryStream(Encoding.UTF8.GetBytes(json)))
        {
            return (T)serializer.ReadObject(ms);
        }
    }
}
private readonly ISerializer serializer;
public EventDispatcher(ISerializer serializer)
{
    this.serializer = serializer;
    InitializeEventsDictionary();
}

private T Deserialze<T>(string json)
{
    return serializer.Deserialze<T>(json);
}

Private Dictionary<string, Action<string>> events = Dictionary<string, Action<string>>();
private void InitializeEventsDictionary()
{
    events.Add(CommitCommentEvent.WebhookEventName, (s) => { OnCommitCommentReceived(() => { return new CommitCommentEventArgs(Deserialze<CommitCommentEvent>(s)); }); });
    events.Add(CreateEvent.WebhookEventName, (s) => { OnCreateReceived(() => { return new CreateEventArgs(Deserialze<CreateEvent>(s)); }); });
    ....
    ....
}

Context

StackExchange Code Review Q#106176, answer score: 4

Revisions (0)

No revisions yet.