real-time web apps & .net. what are your options? ndc oslo 2016
TRANSCRIPT
What we'll cover
1. Why Real-Time?
2. Common Real-Time Use Cases
3. What are your options?
How do you choose? inc. Communication Patterns
.NET examples
Pros & Cons
4. Q&A
5 / 105
@leggetter
Realtime is required when there's a Need orDemand for:
Up to date informationInteraction to maintain engagement (UX)
9 / 105
@leggetter
You All Have Real-Time Data in Your Apps
Every Single Event
Data ChangesSystem InteractionsUser Interactions
19 / 105
@leggetter
Polling Calculations
Scenario
1. Site average of 10,000 Users2. Over 1 Hour, with a 10 second polling interval
23 / 105
@leggetter
Polling Calculations
Scenario
1. Site average of 10,000 Users2. Over 1 Hour, with a 10 second polling interval3. Requests from pages load + HTML, CSS, JS, Images for 10k users = 50,000
23 / 105
@leggetter
Polling Calculations
Scenario
1. Site average of 10,000 Users2. Over 1 Hour, with a 10 second polling interval3. Requests from pages load + HTML, CSS, JS, Images for 10k users = 50,0004. Poll requests per user/minute = (60 / 10) = 6
23 / 105
@leggetter
Polling Calculations
Scenario
1. Site average of 10,000 Users2. Over 1 Hour, with a 10 second polling interval3. Requests from pages load + HTML, CSS, JS, Images for 10k users = 50,0004. Poll requests per user/minute = (60 / 10) = 65. Poll requests per user/hour = (6 * 60) = 360
23 / 105
@leggetter
Polling Calculations
Scenario
1. Site average of 10,000 Users2. Over 1 Hour, with a 10 second polling interval3. Requests from pages load + HTML, CSS, JS, Images for 10k users = 50,0004. Poll requests per user/minute = (60 / 10) = 65. Poll requests per user/hour = (6 * 60) = 3606. Poll requests site wide per hour = (360 * 10,000) = 3,600,000
23 / 105
@leggetter
Polling Calculations
Scenario
1. Site average of 10,000 Users2. Over 1 Hour, with a 10 second polling interval3. Requests from pages load + HTML, CSS, JS, Images for 10k users = 50,0004. Poll requests per user/minute = (60 / 10) = 65. Poll requests per user/hour = (6 * 60) = 3606. Poll requests site wide per hour = (360 * 10,000) = 3,600,000
With polling the site would need to handle 3.65 Million requests per hour
Or 50k HTTP requests + maintain 10k persistent connections?
23 / 105
@leggetter
Cache - clients keep pollingPush Proxy solutions
fanout.iostreamdata.io
Quick Win solutions
24 / 105
@leggetter
2. Use an existing solution
Don't reinvent the wheel
Unless you've a unique use case
25 / 105
@leggetter
Why use an existing solution?
Connection fallback/upgrade hacks still required
WebSocket: 91% of connections
HTTP fallback: 9% of connections
26 / 105
@leggetter
Why use an existing solution?
Connection fallback/upgrade hacks still required
WebSocket: 91% of connections
HTTP fallback: 9% of connections
Support/Community
26 / 105
@leggetter
Why use an existing solution?
Connection fallback/upgrade hacks still required
WebSocket: 91% of connections
HTTP fallback: 9% of connections
Support/Community
Maintenance
26 / 105
@leggetter
Why use an existing solution?
Connection fallback/upgrade hacks still required
WebSocket: 91% of connections
HTTP fallback: 9% of connections
Support/Community
Maintenance
Future features
26 / 105
@leggetter
Why use an existing solution?
Connection fallback/upgrade hacks still required
WebSocket: 91% of connections
HTTP fallback: 9% of connections
Support/Community
Maintenance
Future features
Scaling
26 / 105
@leggetter
Solutions by language
PHP: Ratchet, dNode-php
Java: Netty, Jetty
JavaScript (Node.JS): Faye, Socket.IO (Engine.IO), Primus.io
.NET (C#): SignalR, XSockets
Python: Lots of options built on Tornado
Ruby: em-websocket, Faye
Language agnostic: most hosted services
29 / 105
@leggetter
Client Device Support
Only some have a selection of client libraries
Supported connection transports
31 / 105
@leggetter
Client Device Support
Only some have a selection of client librariesSupported connection transportsHow much data are you sending?
31 / 105
@leggetter
Client Device Support
Only some have a selection of client librariesSupported connection transportsHow much data are you sending?SSL required on 3/4G networks
31 / 105
@leggetter
5. Application/Solution Communication Patterns
How does the client/server & client/client communicate
32 / 105
@leggetter
Simple Messaging
// client
var ws = new WebSocket('wss://localhost/');
ws.onmessage = function(evt) { var data = JSON.parse(evt.data);
35 / 105
@leggetter
Simple Messaging
// client
var ws = new WebSocket('wss://localhost/');
ws.onmessage = function(evt) {
var data = JSON.parse(evt.data);
// ^5
performHighFive();
};
35 / 105
@leggetter
Simple Messaging
// client
var ws = new WebSocket('wss://localhost/');
ws.onmessage = function(evt) {
var data = JSON.parse(evt.data);
// ^5
performHighFive();
};
// server
server.on('connection', function(socket){
35 / 105
@leggetter
Simple Messaging
// client
var ws = new WebSocket('wss://localhost/');
ws.onmessage = function(evt) {
var data = JSON.parse(evt.data);
// ^5
performHighFive();
};
// server
server.on('connection', function(socket){
socket.send(JSON.stringify({action: 'high-5'}));
});
35 / 105
@leggetter
Simple Messaging
using Nexmo.Api;
// SMS
var results = SMS.Send(new SMS.SMSRequest {
from = "15555551212", to = "17775551212",
text = "this is a test"
});
// Voice
var result = Voice.TextToSpeech(new Voice.TextToSpeechCallCommand {
to = "17775551212",
from = "15555551212", text = "Hello from Nexmo"
});
36 / 105
@leggetter
PubSub
// client
var client = new Faye.Client('http://localhost:8000/faye');
client.subscribe('/leggetter-updates', function(data) {
39 / 105
@leggetter
PubSub
// client
var client = new Faye.Client('http://localhost:8000/faye');
client.subscribe('/leggetter-updates', function(data) {
console.log(data.text);});
39 / 105
@leggetter
PubSub
// client
var client = new Faye.Client('http://localhost:8000/faye');
client.subscribe('/leggetter-updates', function(data) {
console.log(data.text);});
client.subscribe('/leggetter-dm-notifications', function(data) { console.log(data.count);});
39 / 105
@leggetter
PubSub
// client
var client = new Faye.Client('http://localhost:8000/faye');
client.subscribe('/leggetter-updates', function(data) {
console.log(data.text);});
client.subscribe('/leggetter-dm-notifications', function(data) { console.log(data.count);});
// server
server.publish('/leggetter-updates', {text: 'Hello DevWeek!'});
39 / 105
@leggetter
PubSub
// client
var client = new Faye.Client('http://localhost:8000/faye');
client.subscribe('/leggetter-updates', function(data) {
console.log(data.text);});
client.subscribe('/leggetter-dm-notifications', function(data) { console.log(data.count);});
// server
server.publish('/leggetter-updates', {text: 'Hello DevWeek!'});
server.publish('/leggetter-dm-notifications', {count: 2});
39 / 105
@leggetter
Evented PubSub
// client
var updates = io('/leggetter-updates');updates.on('created', function (data) { // Add activity to UI});
41 / 105
@leggetter
Evented PubSub
// client
var updates = io('/leggetter-updates');updates.on('created', function (data) { // Add activity to UI});updates.on('updated', function(data) { // Update activity});updates.on('deleted', function(data) { // Remove activity});
41 / 105
@leggetter
Evented PubSub
// client
var updates = io('/leggetter-updates');updates.on('created', function (data) { // Add activity to UI});updates.on('updated', function(data) { // Update activity});updates.on('deleted', function(data) { // Remove activity});
// server
var io = require('socket.io')();var updates = io.of('/leggetter-updates');
41 / 105
@leggetter
Evented PubSub
// client
var updates = io('/leggetter-updates');updates.on('created', function (data) { // Add activity to UI});updates.on('updated', function(data) { // Update activity});updates.on('deleted', function(data) { // Remove activity});
// server
var io = require('socket.io')();var updates = io.of('/leggetter-updates');updates.emit('created', {text: 'PubSub Rocks!', id: 1});
41 / 105
@leggetter
Evented PubSub
// client
var updates = io('/leggetter-updates');updates.on('created', function (data) { // Add activity to UI});updates.on('updated', function(data) { // Update activity});updates.on('deleted', function(data) { // Remove activity});
// server
var io = require('socket.io')();var updates = io.of('/leggetter-updates');updates.emit('created', {text: 'PubSub Rocks!', id: 1});updates.emit('updated', {text: 'Evented PubSub Rocks!', id: 1});updates.emit('deleted', {id: 1});
41 / 105
@leggetter
PubSub
client.subscribe('devexp-channel', function(data) { if(data.eventType === 'chat-message') { addMessage(data.message); }
45 / 105
@leggetter
PubSub
client.subscribe('devexp-channel', function(data) { if(data.eventType === 'chat-message') { addMessage(data.message); } else if(data.eventType === 'channel-purposed-changed') { updateRoomTitle(data.purpose); } else if(/* and so on */) { }})
45 / 105
@leggetter
PubSub
client.subscribe('devexp-channel', function(data) { if(data.eventType === 'chat-message') { addMessage(data.message); } else if(data.eventType === 'channel-purposed-changed') { updateRoomTitle(data.purpose); } else if(/* and so on */) { }})
Evented PubSub
var devexp = io('/devexp-channel');devexp.on('chat-message', addMessage);devexp.on('channel-purposed-changed', updateChannelPurpose);
45 / 105
@leggetter
PubSub
client.subscribe('devexp-channel', function(data) { if(data.eventType === 'chat-message') { addMessage(data.message);
}
else if(data.eventType === 'channel-purposed-changed') { updateRoomTitle(data.purpose);
}
else if(/* and so on */) { }
})
Evented PubSub
var devexp = io('/devexp-channel');devexp.on('chat-message', addMessage);
devexp.on('channel-purposed-changed', updateChannelPurpose);
devexp.on('current-topic-changed', updateChannelTopic);
devexp.on('user-online', userOnline);
devexp.on('user-offline', userOffline);
45 / 105
@leggetter
Data Sync
// client
var ref = new Firebase("https://app.firebaseio.com/doc1/lines");
48 / 105
@leggetter
Data Sync
// client
var ref = new Firebase("https://app.firebaseio.com/doc1/lines");
ref.on('child_added', function(childSnapshot, prevChildKey) { // code to handle new child.});
48 / 105
@leggetter
Data Sync
// client
var ref = new Firebase("https://app.firebaseio.com/doc1/lines");
ref.on('child_added', function(childSnapshot, prevChildKey) { // code to handle new child.});
ref.on('child_changed', function(childSnapshot, prevChildKey) { // code to handle child data changes.});
48 / 105
@leggetter
Data Sync
// client
var ref = new Firebase("https://app.firebaseio.com/doc1/lines");
ref.on('child_added', function(childSnapshot, prevChildKey) { // code to handle new child.});
ref.on('child_changed', function(childSnapshot, prevChildKey) { // code to handle child data changes.});
ref.on('child_removed', function(oldChildSnapshot) { // code to handle child removal.});
48 / 105
@leggetter
Data Sync
// client
var ref = new Firebase("https://app.firebaseio.com/doc1/lines");
ref.on('child_added', function(childSnapshot, prevChildKey) { // code to handle new child.});
ref.on('child_changed', function(childSnapshot, prevChildKey) { // code to handle child data changes.});
ref.on('child_removed', function(oldChildSnapshot) { // code to handle child removal.});
ref.push({ 'editor_id': 'leggetter', 'text': 'Nexmo Rocks!' });
48 / 105
@leggetter
Data Sync
// client
var ref = new Firebase("https://app.firebaseio.com/doc1/lines");
ref.on('child_added', function(childSnapshot, prevChildKey) { // code to handle new child.});
ref.on('child_changed', function(childSnapshot, prevChildKey) { // code to handle child data changes.});
ref.on('child_removed', function(oldChildSnapshot) { // code to handle child removal.});
ref.push({ 'editor_id': 'leggetter', 'text': 'Nexmo Rocks!' });
Framework handles updates to other clients
48 / 105
@leggetter
RMI
// client
var chat = $.connection.chatHub;
chat.client.broadcastMessage = function (name, message) { // handle message
};
51 / 105
@leggetter
RMI
// client
var chat = $.connection.chatHub;
chat.client.broadcastMessage = function (name, message) { // handle message
};
chat.server.send( 'me', 'hello world' );
51 / 105
@leggetter
RMI
// client
var chat = $.connection.chatHub;
chat.client.broadcastMessage = function (name, message) { // handle message
};
chat.server.send( 'me', 'hello world' );
$.connection.hub.start(); // async
51 / 105
@leggetter
RMI
// client
var chat = $.connection.chatHub;
chat.client.broadcastMessage = function (name, message) {
// handle message
};
chat.server.send( 'me', 'hello world' );
$.connection.hub.start(); // async
// server
public class ChatHub : Hub
{
51 / 105
@leggetter
RMI
// client
var chat = $.connection.chatHub;
chat.client.broadcastMessage = function (name, message) {
// handle message
};
chat.server.send( 'me', 'hello world' );
$.connection.hub.start(); // async
// server
public class ChatHub : Hub
{
public void Send(string name, string message)
{
51 / 105
@leggetter
RMI
// client
var chat = $.connection.chatHub;
chat.client.broadcastMessage = function (name, message) {
// handle message
};
chat.server.send( 'me', 'hello world' );
$.connection.hub.start(); // async
// server
public class ChatHub : Hub
{
public void Send(string name, string message)
{
// Call the broadcastMessage method to update clients.
Clients.All.broadcastMessage(name, message);
}
}
51 / 105
@leggetter
Codehttps://github.com/leggetter/realtime-dotnet-examplesShort link: http://j.mp/rt-dotnet-ex
58 / 105
@leggetter
What we'll look at:
PM> Install-Package Microsoft.AspNet.SignalR
Scripts\jquery.signalR*.js
App_Start\SignalRStartup.cs
Controllers\HomeController.cs
Hubs\ChatHub.cs
Views\Home\SignalR.cshtml
Script\chat\SignalRChat.js
62 / 105
@leggetter
Pros
.NET
Simple integration
MS Supported
jQuery Dependency
Cons
Tightly coupled
RMI only
Self-Scaling
Scaling (realtime + HTTP)
Self-Hosted Demo 1: Pro & Cons
63 / 105
@leggetter
What we'll look at:
PM> Install-Package XSockets
App_Start\XSocketsStartup.cs
Controllers\HomeController.cs
XSockets\ChatController.cs
Views\Home\XSockets.cshtml
Scripts\XSockets.latest.js
Script\chat\XSocketsChat.cs
65 / 105
@leggetter
Pros
.NET
Simple integration
Communication patterns
PubSub/Evented
RMI
Licensed
Cons
Tightly coupled
Self-Scaling
Scaling (realtime + HTTP)
Licensed
Self-Hosted Demo 2: Pro & Cons
66 / 105
@leggetter
Pros
.NETMaps well to PubSubLoosely coupledCould use another runtime
Cons
How does it fit with RMI/SignalR?Multiple componentsSelf-scalingQueue routing questionsIn: HTTP. Out: WebSocket
Self-Hosted: .NET + Message Queue - Pro &
Cons
69 / 105
@leggetter
If SignalR or XSockets aren't a good fityou may have to look at a non-.NET solution
70 / 105
@leggetter
What we'll look at:
PM> Install-Package Pubnub
Controllers\ChatController.cs
Views\Home\PubNub.cshtml
Script\chat\PubNub.js
PubNub Real-Time Analytics
73 / 105
@leggetter
Pros
Simple & powerfulInstantly scalableManaged & dedicatedDirect integration. No overhead.
Cons
3rd party relianceDifficult to influence functionality
Hosted - Pros & Cons
74 / 105
@leggetter
Why use a hosted service?Scenario
1. Site average of 10,000 Users2. Over 1 Hour, no polling
75 / 105
@leggetter
Why use a hosted service?Scenario
1. Site average of 10,000 Users2. Over 1 Hour, no polling3. Requests from pages load + HTML, CSS, JS, Images for 10k users = 50,000
75 / 105
@leggetter
Why use a hosted service?Scenario
1. Site average of 10,000 Users2. Over 1 Hour, no polling3. Requests from pages load + HTML, CSS, JS, Images for 10k users = 50,0004. That's it! Total: 50,000
75 / 105
@leggetter
Why use a hosted service?Scenario
1. Site average of 10,000 Users2. Over 1 Hour, no polling3. Requests from pages load + HTML, CSS, JS, Images for 10k users = 50,0004. That's it! Total: 50,000
Your servers handle 50k requests per hour instead of 3.6M
You offload the polling or persistent connections to the service
75 / 105
@leggetter
How do you choose?
7 Realtime Framework Considerations
1. Should you keep on polling?
2. Use an Existing Solution
3. Use a language you're comfortable with
4. Do you need multiple client device support?
5. Simple Messaging, PubSub/Evented, RMI or DataSync
6. Architectural considerations
7. Hosted v Self-Hosted (Build vs. Buy)
78 / 105
@leggetter
You need Real-Time!
There are lots of options.
Make the choice that's right for you.
I hope this helps!
79 / 105
@leggetter
Resources
Real-time Tech Guidegithub.com/leggetter/realtime-dotnet-examplesTools, Tips and Techniques for Developing Real-time AppsNexmo
80 / 105
@leggetter
Real-time Web Apps & .NET What are your options?
Questions?
PHIL @LEGGETTER
Head of Developer Relations
81 / 105
@leggetter
Bayeux
DDP
dNode
EPCP
GRIP
gRPC
MQTT
Pusher Protocol
STOMP
SignalR Protocol
WAMP (Web App Messaging Protocol)
XMPP (various)
Communication Pattern ProtocolStandardisation
84 / 105
@leggetter
Firebase
GitHub
Iron.io
MailChimp
MailJet
PagerDuty
Nexmo
SendGrid
Real-Time APIs
86 / 105
@leggetter
A thing can be anything
SensorsAppliancesVehiclesSmart PhonesDevices (Arduino, Electric Imp, Raspberry Pi etc.)
91 / 105
@leggetter
A thing can be anything
SensorsAppliancesVehiclesSmart PhonesDevices (Arduino, Electric Imp, Raspberry Pi etc.)ServersBrowsersApps: Native, Web, running anywhere
91 / 105
@leggetter
The Majority of code we'll write will still befor "Apps"
ConfiguringMonitoringInteractingApp Logic
92 / 105
@leggetter
Real-Time Use Case Evolution
Notifications & SignallingActivity StreamsData Viz & PollsChatCollaborationMultiplayer Games
93 / 105
@leggetter
Notifications/Activity Streams -> Actions
94 / 105
@leggetterThe end of apps as we know it - Intercom
600M MAUs10M integrationsapp-within-an-app modeltaxi, order food, tickets, games etc.
99 / 105
@leggetter