Hello Integration
Last updated
Last updated
This is the last of the hello series. We're going to cover the final few topics you'll need to really get started. Let's jump in!
Watermarking is simply storing a key/value pair from last execution and recalling it again next time it is needed. We use this when interfacing with another system we need to synchronize data to or from. A few examples of this are:
A Mail sync client needs to know what the UIDNEXT, or MODSEQ integers are.
If you're synchronizing data changes to SharePoint, you'll want to store the delta link as a string.
If you're pulling data by a last modified date, you'll want to make sure you store a DateTime(Offset).
These are just a few examples of how you would use watermarking in a real world production application.
We make working with watermarks quite easy and they are very powerful.
Our watermarks are persisted locally to disk automatically and recalled on application startup.
They are debounced (We cover this in the ).
They are thread safe.
You can register as many subscribers as you need to the data feed.
You may push that data externally on update, and recall it on initialization for further backup.
Registering a watermark is an easy one liner. Let's look at the parameters:
Line 4 - Name - The name of the watermark
Line 7 - Func<Watermark> Initialization callback - If the value does not exist, the initialization callback is called and an initial value is set.
Line 10 - Action<Watermark> onUpdate? - An optional callback for when the value is updated.
To create the other base data types, simply provide that type in the initialization callback.
That's it. The underlying system does the heavy lifting of persisting this locally under the /watermarks
folder beside it's running application.
You're able to register as many "subscribers" to the data changes you desire in other parts of the code. These will be called on a background thread with the updated value.
The RegisterAdditionalEventHandler
simply takes two parameters:
Line 4 - Name - The name of the watermark you want to update.
Line 7 - EventHandler<Watermark> - This sends you the (sender, watermark)
back in the callback and you're able to read the value from there
The UpdateWatermark
call takes two parameters:
Line 4 - Name - The name of the watermark you want to update.
Line 7 - Watermark - The new watermark and value.
A lot happens behind the scenes you should be aware of:
Updates are debounced which means that rapid updates don't all push their values at once to any subscribers of the data updates.
Let's say you processed 10 records in a matter of 50 milliseconds and every record you updated the watermark so it went from the initial value of 0
to 10
.
After about a second, all of the subscribers would get a single updated watermark event with the value of 10
.
Debouncing reduces the number of calls both to the filesystem and to your subscribers
Updates are thread safe as they perform internal item locks before writing to disk and notifying subscribers.
Getting the watermark only requires the Name of the watermark to retrieve.
If there are inflight updates to the watermark, this call may block the thread for a second or two while those debounces get processed. If there are no inflight updates, it returns immediately.
Another primary concept of writing integrations is using credentials to get access tokens and authorization for other systems. The most common example of this would be using OAuth 2.0 to retrieve an access or bearer token that expires in the future.
Credentials are all registered on startup and are usually given an expiration date. This allows for any process later in the application to ask the manager to retrieve a credential. The below example shows how to register SuperCredential, and two possible return values:
When the application loads up or a new credential is created, the local drive is checked and all previously non expired credentials are restored immediately. This allows for applications to be safely shut down and restarted without interrupting or over-retrieving from an external API.
During the retrieval process, the CredentialStore checks for existence of the credential, it's expiration and then checks if the expiration is within N number of minutes(user defined) before returning to the caller.
In Summary: the CredentialStore does several important things:
It automatically caches the authorization details to disk and retrieves them again on application reload.
When supplying expiration dates to a credential, it's able to renew itself when it's required before sending back the old authorization parameters.
The retrieval call is automatically retried to prevent a blip in connectivity from stopping a process.
It will automatically renew the token a defined number of minutes before expiration, allowing long running processes not to fail while in the middle of execution.
It integrates seamlessly with RestSharp, allowing all HTTP(S) traffic to automatically and internally pull credentials
To register a specific RestSharp credential, just create that type and assign the appropriate Authenticator.
Any time you create a client, simply assign this credential authenticator:
There's a whole page on this content, read more here:
Another integration pattern is limiting the number of calls that can be made during a given period of time. This is important when rate limiting or billing may be an issue and is a perfect stop gap for a critical failure causing an erroneous number of API calls or running up an unwanted bill!
The Limiter auto resets it's count every calendar day and will not allow additional executions past the defined limit.
A common integration issue is that systems outside of the immediate domain will have faults, issues, disconnects, exceptions, availability or downtime. All of these issues are usually easily resolved with a fault tolerance approach to building your application.
Perigee has several classes built into it to make this a common practice within your application.
The Cancellation Token passed alongside every ManagedThread is an important fault tolerant key to be aware of. They allow us to gracefully shut down or stop processing data, and give us an opportunity to save state before the application closes.
If you can successfully pay attention to them, they give you the opportunity you need to end your process and save state.
Use the Exit Event alongside the cancellation tokens to store and finalize state on shut down, this allows for an easy application reload, update, or server restart. Remember Exit is called immediately before the application closes, and in some cases only allows for a few seconds to pass before ending execution. This is a last step process where data should be persisted.
One of the utility class methods is a Retry and RetryAsync. They provide an easy ability to retry on exception thrown with a delay.
The response from these classes is a Tuple where
Item1
is a boolean indicating if it was a success(true), or failed the maximum number of times (false).
Item2
is the exception thrown if it was not a succeed
A lot of cases network connectivity issues can easily be dealt with by simply retrying a moment later. These handy extensions are built right alongside RestSharp to use in place. If they don't suit your needs, you can always wrap the call chain in the utility retry methods instead!
In the example below: ExecuteRetry(request, count)
will execute the request at least once.
If a success response code is sent back, it will return and not execute the retry.
If a failure code is returned it will retry as many times as supplied with a 5 second delay between requests.
If using the CredentialAuthenticator
and a 401 (unauthorized) status is returned, it will detect and invalidate the credential. This will refresh the authorization token/mechanism and retry again with the new token.
This provides a fantastic way to keep state or progress on any tasks ongoing.
If you run the below demo multiple times, you'll see the count being restored, incremented and shut down. There's a new file with the data located at bin\debug\sync\local.json
.
The class used above is simply two properties with getter/setters:
Thread Conditions provide a way to cancel processes if a condition is not met. It's a great way to turn off a service is the network is down or the hard drive is too full. They're fully customizable and allow you to set your own logic for how they should be run.
Transaction Coordinator is the big boss of all of the above methods. It implements cancellation tokens, exit events, retries, the FSW modal, and local+remote two way synchronization.
Think of the coordinator as a way of defining the steps of a process in logical tiny blocks, and always verifying you move through those blocks in the correct order while never missing a step, or data in the process.
It's so fault tolerant you can terminate the running application, and restart it at any point and it will pick up where it left off. Impressive eh?
The register call should be performed at application initialization if possible to avoid a of pulling for it's value before it is registered. Requesting a watermark before it is registered will throw an exception .
This is built off of the module listed below, and code for it is linked if you'd like to customize it to match your needs.
FSW for short is the primary class suited for synchronizing data to the local drive. . In short, you supply the CancellationToken
from the thread and it safely handles the file locking and writing of newly updated data on a regularly scheduled basis and before the application closes.
To see this better in action, !