Skip to content

Instantly share code, notes, and snippets.

@CraigCav
Created April 8, 2012 18:51
Show Gist options
  • Save CraigCav/2339152 to your computer and use it in GitHub Desktop.
Save CraigCav/2339152 to your computer and use it in GitHub Desktop.
Patient Waiting times brain dump...
public class ReferralToTreatmentPolicy : Saga<ReferralToTreatmentSagaData>,
IAmStartedByMessages<PatientReferredToConsultantLedService>,
IHandleMessages<TreatmentStarted>,
IHandleMessages<PatientAddedToTransplantList>,
IHandleMessages<PatientDeclinedTreatment>,
IHandleTimeouts<ReferralToTreatmentPeriodBreach>
{
public override void ConfigureHowToFindSaga()
{
ConfigureMapping<TreatmentStarted>(s => s.UBRN, m => m.UniqueBookingReferenceNumber);
ConfigureMapping<PatientAddedToTransplantList>(s => s.UBRN, m => m.UniqueBookingReferenceNumber);
}
public void Handle(PatientReferredToConsultantLedService message)
{
this.Data.UBRN = message.UniqueBookingReferenceNumber;
this.Data.ReferralToTreatmentPeriodStart = message.ReferralRequestReceivedDate;
RequestUtcTimeout<ReferralToTreatmentPeriodBreach>(TimeSpan.FromDays(127),
x => x.BreachDate = message.ReferralRequestReceivedDate.AddDays(127));
}
public override void Timeout(ReferralToTreatmentPeriodBreach state)
{
Bus.Publish<ReferralToTreatmentPeriodBreached>(x =>
x.UBRN = Data.UBRN,
x.ReferralToTreatmentPeriodStart = Data.ReferralToTreatmentPeriodStart,
x.ReferralToTreatmentPeriodBreachDate = x.BreachDate);
}
public void Handle(TreatmentStarted message)
{
if(message.Date < ReferralToTreatmentPeriodStart)
throw new InvalidOperationException("Treatment cannot start before the referral");
Bus.Publish<ReferralToTreatmentPeriodComplete>(x =>
x.UBRN = Data.UBRN
x.ReferralToTreatmentPeriodStart = Data.ReferralToTreatmentPeriodStart,
x.ReferralToTreatmentPeriodEnd = message.Date);
MarkAsComplete();
}
public void Handle(PatientAddedToTransplantList message)
{
if(message.Date < ReferralToTreatmentPeriodStart)
throw new InvalidOperationException("Patient cannot be added to transplant list before the referral");
Bus.Publish<ReferralToTreatmentPeriodComplete>(x =>
x.UBRN = Data.UBRN,
x.ReferralToTreatmentPeriodEnd = message.Date);
MarkAsComplete();
}
public void Handle(PatientDeclinedTreatment message)
{
if(message.Date < ReferralToTreatmentPeriodStart)
throw new InvalidOperationException("Patient cannot decline treatment before the referral");
Bus.Publish<ReferralToTreatmentPeriodComplete>(x =>
x.UBRN = Data.UBRN,
x.ReferralToTreatmentPeriodEnd = message.Date););
MarkAsComplete();
}
}
public interface IAffectReferralToTreatmentTimes
{
Guid UniqueBookingReferenceNumber { get; set; }
}
public interface PatientReferredToConsultantLedService : IAffectReferralToTreatmentTimes
{
Datetime ReferralRequestReceivedDate { get; set; }
}
public interface PatientReferredToOnwardReferralService : PatientReferredToConsultantLedService {}
public interface PatientSelfReferredToConsultantLedService : PatientReferredToConsultantLedService {}
public interface TreatmentStarted : IAffectReferralToTreatmentTimes
{
Datetime Date { get; set; }
}
public interface PatientAddedToTransplantList: IAffectReferralToTreatmentTimes
{
Datetime Date { get; set; }
}
public interface PatientDeclinedTreatment: IAffectReferralToTreatmentTimes
{
Datetime Date { get; set; }
}
public class ReferralToTreatmentSagaData : ISagaEntity
{
public Guid Id { get; set; }
public string Originator { get; set; }
public string OriginalMessageId { get; set; }
[Unique]
public Guid UBRN { get; set; }
public DateTime ReferralToTreatmentPeriodStart { get; set; }
}
@CraigCav
Copy link
Author

CraigCav commented Apr 8, 2012

So continuing on from our previous discussion, I figured I'd see if I could direct our investigation based on some real requirements pulled from a past project Paul and I worked on. I've found a relatively clear (for the NHS) overview of how patient waiting times are monitored here. The document included a pretty extensive set of case studies we can draw up user stories from.

So that we've got something to talk about, I've started off a very basic implementation. It completely ignores "clock-pauses" for example, to keep things simple.

Things to note:

  1. Exceptions are raised to prevent the system transitioning into a nonsensical state (e.g. it doesn't make sense for a patient to be treated before their referral)
  2. Event contracts are kept light-weight and free of data that has no baring on this story. I wouldn't be surprised however, if additional data would be included in the serialized message (for instance, the serialized message for TreatmentStarted may include the patient NHS number). Interface composition is our friend here.

@PaulUpson
Copy link

Well, I'm not sure what you want me to tell you about this. I like how simple it appears. Is this based of Udi's saga pattern coz it looks familiar?
I'd maybe like to see some specs/tests surrounding these behaviours but with so few moving parts I can't see it being a problem area.

I can see that this saga has one start condition, one timeout and 3 end conditions. I'm guessing that the MarkAsComplete() terminates the saga, but I'd be interested to see how the the ConfigureMapping actually ensures that the correct saga data is provided to the handlers of subsequent messages.

maybe "next steps" are looking at including clock-pauses?

@CraigCav
Copy link
Author

The implementation in using NServiceBus (ish - it probably doesn't compile) and it's based on my understanding of how Udi describes sagas.

One thing I was exploring with this implementation (and something I'm not sure about yet) is that rather than delegating to an entity to act as the aggregate root, the saga is instead taking this responsibility. This seemed appropriate as the business rules seemed very policy based. That said, I know typically sagas are supposed to act on events and send commands; in my example they act on and send events.

I've toyed with turning the policy into an aggregate root too following a similar approach to Lokad (I'll make this code available on github if you like). This codebase has some specs around it too, but again, something I'm not fully comfortable with is that even in this approach, the aggregate is reacting to events rather than commands. I think this has happened as I want to keep the implementation of waiting times separate from the parts of the system that raise the events (which I would assume come from separate bounded contexts)...but something about this has got my spidey-sense tingling. That said, sagas are meant for collaborations across contexts (from what i'm led to believe)/

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment