Doorbell Part 2
In this section, we'll be making some additions to the Doorbell code. We'll show off some of JobFlow more advanced rule filtering and job creation.
Sending a Notification
Whenever the doorbell is pressed, let's send a notification as well as ringing the chime. So far, we've spawned a single job in each rule. However, we're able to create as many jobs as we want. So, let's modify the RingBell
rule to spawn a notification job.
[FlowRule("Doorbell")]
public class RingBellRule : JobFlowRule<DoorbellPressedMessage>
{
protected override void DefineRule()
{
ICacheManager cache = null;
Dependency()
.Resolve(() => cache);
Document<DoorbellPressedMessage> document = null;
When()
.HaveDocument(() => document, _ => _.Match(
_ => _.Data.PreviousTimeStamp != null,
_ => (_.Data.TimeStamp - _.Data.PreviousTimeStamp.Value).TotalSeconds > 5));
Then()
.StartJob("SendDoorbellEmail", () => new JobDefinitionSettings { ScheduleDateTime = DateTime.UtcNow.AddSeconds(10), ConnectionName = "SendEmail" })
.Do(ctx => UpdateCache(ctx, cache, document));
}
private void UpdateCache(IJobFlowContext ctx, ICacheManager cache, Document<DoorbellPressedMessage>? document)
{
cache.Set("Doorbell:PreviousPress", document.Data.TimeStamp);
}
}
When adding the second job, we make use of the JobDefinitionSettings
class - in particular, we're setting a schedule and specifying a connection name. Because Rule classes are instantiated only once, the JobDefinitionSettings
object is create via a lambda expression that is executed every time the Rule executes. This ensures each execution creates a different instanse of the JobDefinitionSettings
.
By setting the ScheduleDateTime
property, the system will use the provided DateTime
to determine when to schedule the job. In this case, we're sending the email 10 seconds after the doorbell is pressed. By specifying a connection, rather than relying on the job's name, we can reuse connections for multiple jobs. Therefore, simply adding that extra bit of code, we're now able to spawn multiple jobs at once, all with custom settings.
We are also setting the ConnectionName
property to "SendEmail," which demonstrates potentially using the same connection for different jobs. In this case, "SendEmail" could be used by more than just the "SendDoorbellEmail" Job. Setting ConenctionName
will have JobFlow load the Transport with the name "SendEmail" when normally JobFlow would look for the "SendDoorbellEmail" Transport configuration.
Job Properties
In some cases, you may want to pass additional one-off pieces of data to the job. The JobDefinitionSettings
class has a Data
property that can be used. The data set here is passed to the worker via the JobModel.Data
property.
[FlowRule("Doorbell")]
public class RingBellRule : JobFlowRule<DoorbellPressedMessage>
{
protected override void DefineRule()
{
ICacheManager cache = null;
Dependency()
.Resolve(() => cache);
Document<DoorbellPressedMessage> document = null;
When()
.HaveDocument(() => document, _ => _.Match(
_ => _.Data.PreviousTimeStamp != null,
_ => (_.Data.TimeStamp - _.Data.PreviousTimeStamp.Value).TotalSeconds > 5));
Then()
.StartJob("SendDoorbellEmail",
() => new JobDefinitionSettings
{
ScheduleDateTime = DateTime.UtcNow.AddSeconds(10),
ConnectionName = "SendEmail",
Data = new { Subject = "Doorbell Rang" }.ToJObject()
})
.Do(ctx => UpdateCache(ctx, cache, document));
}
private void UpdateCache(IJobFlowContext ctx, ICacheManager cache, Document<DoorbellPressedMessage>? document)
{
cache.Set("Doorbell:PreviousPress", document.Data.TimeStamp);
}
}
In this example, the property is being set to a constant; however, this setup would allow for any calculated value. We are also making use of JObject, though that is not necessary. By using JObject, we can let additional rules add additional properties (as we'll see in the next section).
NRules
So far, we've been using standard JobFlow matching methods (such as IsStart
or FromCompletedJob
). These are only convenience methods - we're able to match on nearly anything, since we're able to use the entire NRules DSL. For instance, let's say for every email we're sending out, regardless of the step that spawns the job, we want to include a standard CC address.
For this next Rule
, we rely are two bits of information:
- For every new
Job
created, JobFlow adds theJobDefinition
(or, in most cases,DispatchJobDefinition
) to the NRules context. - NRules has forward chaining of rules, meaning any new "Facts" (objects) added to the NRules Context will cause any matching rules to be automatically picked up.
[FlowRule("All")]
public class SendEmailCC : JobFlowRule
{
protected override void DefineRule()
{
DispatchJobDefinition newJob = null;
When()
.Match(() => newJob,
_ => _.Settings != null && _.Settings.ConnectionName == "SendEmail");
Then()
.Do(_ => Action(newJob));
}
private void Action(DispatchJobDefinition newJob)
{
newJob.Settings.Data["CC"] = "cc@here.com";
}
}
As you can see in the Define method, we're looking for any new DispatchJobDefinition
with the criteria that the JobDefinitionSettings
has a ConnectionName
set to "SendEmail." Also note, we're using the DocumentRule
attribute with "All" - the system will include this rule in all document rulesets. In this way, we can add an automatic CC to all outgoing emails.