Question

Say I have something like a support ticket system (simplified as example). It has many users and organizations. Each user can be a member of several organizations, but the typical case would be one org => many users, and most of them belong only to this organization. Each organization has a "tag" which is to be used to construct "ticket numbers" for this organization. Lets say we have an org called StackExchange that wants the tag SES.

So if I open the first ticket of today, I want it to be SES140407-01. The next is SES140407-02 and so on. Doesn't have to be two digits after the dash.

How can I make sure this is generated in a way that makes sure it is 100% unique across the organization (no orgs will have the same tag)?

Note: This does not have to be the document ID in the database - that will probably just be a Guid or similar. This is just a ticket reference - kinda like a slug - that will appear in related emails etc. So it has to be unique, and I would prefer if we didn't "waste" the sequential case numbers hilo style.

Is there a practical way to ensure I get a unique ticket number even if two or more people report a new one at almost the same time?

EDIT: Each Organization is a document in RavenDB, and can easily hold a property like LastIssuedTicketId. My challenge is basically to find the best way to read this field, generate a new one, and store this back in a way that is "race condition safe".

Another edit: To be clear - I intend to generate the ticket ID in my own software. What I am looking for is a way to ask RavenDB "what was the last ticket number", and then when I generate the next one after that, "am I the only one using this?" - so that I give my ticket a unique case id, not necessarily related to what RavenDB considers the document id.

Was it helpful?

Solution

I use for that generic sequence generator written for RavenDB:

public class SequenceGenerator
{
  private static readonly object Lock = new object();
  private readonly IDocumentStore _docStore;

  public SequenceGenerator(IDocumentStore docStore)
  {
    _docStore = docStore;
  }

  public int GetNextSequenceNumber(string sequenceKey)
  {
    lock (Lock)
    {
      using (new TransactionScope(TransactionScopeOption.Suppress))
      {
        while (true)
        {
          try
          {
            var document = GetDocument(sequenceKey);
            if (document == null)
            {
              PutDocument(new JsonDocument
              {
                Etag = Etag.Empty,
                // sending empty guid means - ensure the that the document does NOT exists
                Metadata = new RavenJObject(),
                DataAsJson = RavenJObject.FromObject(new { Current = 0 }),
                Key = sequenceKey
              });
              return 0;
            }

            var current = document.DataAsJson.Value<int>("Current");
            current++;

            document.DataAsJson["Current"] = current;
            PutDocument(document);

            {
              return current;
            }
          }
          catch (ConcurrencyException)
          {
            // expected, we need to retry
          }
        }
      }
    }
  }

  private void PutDocument(JsonDocument document)
  {
    _docStore.DatabaseCommands.Put(
      document.Key,
      document.Etag,
      document.DataAsJson,
      document.Metadata);
  }

  private JsonDocument GetDocument(string key)
  {
    return _docStore.DatabaseCommands.Get(key);
  }
}

It generates incremental unique sequence based on sequenceKey. Uniqueness is guaranteed by raven optimistic concurrency based on Etag. So each sequence has its own document which we update when generate new sequence number. Also, there is lock which reduced extra db calls if several threads are executing at the same moment at the same process (appdomain).

For your case you can use it this way:

var sequenceKey = string.Format("{0}{1:yyMMdd}", yourCompanyPrefix, DateTime.Now);
var nextSequenceNumber = new SequenceGenerator(yourDocStore).GetNextSequenceNumber(sequenceKey);
var nextSequenceKey = string.Format("{0}-{1:00}", sequenceKey, nextSequenceNumber);
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top