What would the design look like for a class that implements a threaded email sending queue?

170 views Asked by At

So my application is a taxi call dispatch system, in which an operator receives calls over the phone and assigns received calls to a driver. On the Call Details form where the call detail is entered, as soon as the Save button is clicked, the form should close, should send an email to the taxi cab driver in a background thread, and the main form is shown, where he can select the next call and assign it to another driver.

The hard bit about the design is that it should be a queue, meaning an email in the queue needs to wait until processing for an email that is being sent has completed. Of course the email sending is happening in a different thread than the main VCL thread.

There only response back to the main thread would be the number of emails remaining to be sent in the queue, via a function call from the main thread to the emailer thread. Eg. if the user tries to close the application, it will ask the emailer queue class if there are any emails pending in the queue.

In Delphi, what class design and constructs will best implement this functionality? I'm sure it has one or more TThreads, but I'm lost beyond that. I could have started writing something, but I'm sure it would have been wrong, and I would need to tear it apart, post here and start again based on recommendations. That is why I posted here first - I want design tips. Since I'm using Delphi, I posted here because I want Delphi-specific design tips. Thanks in advance for any answers.

Note: I'm open to using either a TThread implementation or OmniThreadLibrary.

I'm including a basic template for the class, which the answer will need to expand and build upon.

unit uEmailQueue;

interface

uses Classes;

type
  TEmailServer = record
    SMTPHost: String;
    SMTPPort: Integer;
    SMTPUseSSL: Boolean;
    SMTPUserName: String;
    SMTPPassword: String;
    SMTPSenderName: String;
  end;

  TEmailMessage = record
    RecipientEmailAddr: String;
    EmailSubject: String;
    EmailMessage: String;
  end;

  TEmailQueue = class(TObject)
  private
    FEmailServer: TEmailServer;
  public
    procedure SendEmail(AEmailMessage: TEmailMessage);
  end;

implementation

{ TEmailQueue }

procedure TEmailQueue.SendEmail(AEmailMessage: TEmailMessage);
begin

end;

end.
1

There are 1 answers

1
Arioch 'The On BEST ANSWER

I suggest you to set up OTL.

You would most probably have the only SMTP server, so the only mail-sending thread, in case you would later add more notification sending servers ( like sending SMSes to different phone operators ) - you would add more outgoing "sink" threads.

Personally I would think about using the Pipeline pattern.

For now it looks mostly an over-engineering, but it would later provide you for customisations and extensions (like adding more notification means than sending e-mail, like logging of the process, like adding "VIP client/regular client/shady client/fraud" heuristics) or anything else.

Since the Pipeline starts with some thread-safe source data collection/container, to start the processing of the new request you would only need to put the task record into that queue.

Something like

 var TaxiInQueue: iOmniBlockingCollection;
 ....
 var NewTask: TEmailMessage;
 ...
 TaxiInQueue.Add( TOmniValue.FromRecord(NewTask) );

Again, that would provide you to extend from single-thread GUI application (with a single agent putting in new tasks) to a network agent (3-tier or WWW-servlet) with several threads inputting new tasks simultaneously from different connections/threads.

There only response back to the main thread would be the number of emails remaining to be sent in the queue

Personally I would do it in a "double accounting" way, i would keep two variables: total tasks created and total tasks finished. They can be managed by a separate invisible window in the main thread (AllocateHWND and using PostMessage to trigger increments), or a separate worker thread (over-engineering probably), or just some object with any kind of MREW-lock in it.

The delta would be the current (or slightly outdated) queue length. Also that windows/thread/object would be able to generate events of his own (like painting some meters if the queue is idle, normal, or overflowing)

Regarding MREW locks, there is a stock one in Delphi RTL, there is another one in OTL library itself, and there was some guy who ported OmniMREW to Lzarus, and then made another lock that he claims is even more efficient.