4. - Biggest provider in sport offering
- Supports a lot of regulated markets
- About 1000 microservices (200 distinct types)
- 5 datacenters maintained fully by SBTech
- About 500 concurrent live events at pick time
- On average we handle about 100K+ RPS
8. If we have sexy C#
class Person
{
public string FirstName; // Not null
public string? MiddleName; // May be null
public string LastName; // Not null
}
9. If we have sexy C#
switch (shape)
{
case Circle c:
WriteLine($"circle with radius {c.Radius}");
break;
case Square s when (s.Length == s.Height):
WriteLine($"{s.Length} x {s.Height} square");
break;
case Rectangle r:
WriteLine($"{r.Length} x {r.Height} rectangle");
break;
}
10. If we have sexy C#
public (string, string) LookupName(long id)
{
// some code..
return (first, middle);
}
var names = LookupName(id);
WriteLine($"found {names.first} {names.last}.");
13. Story of a new C# project
Get("/api/bets", async request =>
{
var result = Validator.Validate(request); // unit testable
if (result.IsOk)
{
var response = await _httpClient.Call(request);
return BetsMapper.MapResponse(response); // unit testable
}
else return Errors.MapError(result); // unit testable
});
15. public class BetsProviderService : IBetsProviderService
{
readonly IOpenBetsRepository _openBetsRepository;
readonly ISettledBetsRepository _settledBetsRepository;
public BetsProviderService(IOpenBetsRepository openBetsRepository,
ISettledBetsRepository settledBetsRepository)
{
_openBetsRepository = openBetsRepository;
_settledBetsRepository = settledBetsRepository;
}
}
Story of a new C# project
22. Recursive modules
type Plane() = member x.Start() = PlaneLogic.start(x)
// types and modules can't currently be mutually
// referential at all
module private PlaneLogic =
let start (x: Plane) = ...
28. Community growth
• To begin, F# has grown to be bigger than ever, at least as far as
we can measure, through product telemetry, twitter activity,
GitHub activity, and F# Software Foundation activity.
• Active unique users of F# we can measure are in the tens of
thousands.
• Measured unique users of Visual Studio Code
with Ionide increased by over 50% this year, to become far larger
than ever.
• Measured unique users of Visual Studio who use F# increased by
over 20% since last year to be larger than ever, despite quality
issues earlier in the year that we believe have inhibited growth.
• Much of the measured growth coincides with the release of .NET
Core 2.0, which has shown significant interest in the F#
community.
34. I need an API
to build
unique UI
The Problem
Oops?!
35. The Problem
Oops?!
1. well defined contracts (Event, Market, Selection)
2. flexible way to query data and subscribe on changes
3. push updates notifications about changes
4. near real time change delivery (1 sec delay)
We need to provide:
40. Subscribers
type NodeName = string
type LastHeartBeat = DateTime
type NodeSubscribers = Map<NodeName, LastHeartBeat>
type FeedStatus =
| ReadyForDeactivation
| HasSubscribers
| NoSubscribers
type ChangeFeed = {
Id: FeedId
View: FeedView
ChangeLog: ChangeLog
Subscribers: NodeSubscribers
Query: Query
Status: FeedStatus
}
Query
Feed View
2:2
1:0 Chelsea - Milan
Milan - Liverpool
Change Feed
Change Log
41. module ChangeFeed
let getSnapshot (feed: ChangeFeed, fastPreloadAmount: uint32) = ...
let getUpdates (feed: ChangeFeed, lastOffset: uint32) = ...
let getLatestUpdate (feed: ChangeFeed) = ...
let subscribe (feed: ChangeFeed, name: NodeName, currentTime: DateTime) = ...
let subscribeByOffset (feed: ChangeFeed, name: NodeName, lastOffset: uint32) = ...
let unsubscribe (feed: ChangeFeed, name: NodeName) = ...
let getInactiveNodes (feed: ChangeFeed, currentTime: DateTime, inactivePeriod: TimeSpan) = ...
let reloadView (feed: ChangeFeed, queryResults: QueryResult seq) = ...
42. Feed View
2:2
1:0 Chelsea - Milan
Milan - Liverpool
type PartialView = {
EntityType: EntityType
Entities: IEntity seq
}
type FeedView = {
OrderType: EntityType
OrderIds: string seq
OrderFilter: FilterFn option
Views: Map<EntityType, PartialView>
}
let tryCreate (queryResults: QueryResult seq, orderType: EntityType,
orderFilter: FilterFn option) =
match queryResults with
| NotMatchFor orderType -> fail <| Errors.OrderTypeIsNotMatch
| Empty -> ok <| { OrderType = orderType; OrderIds = Seq.empty
OrderFilter = orderFilter; Views = views }
| _ -> ok <| ...
43. [<Property>]
let ``empty ChangeLog should start with offset 0 and requested maxSize`` (maxSize: uint32) =
let changeLog = ChangeLog.create(maxSize)
Assert.Equal(0u, changeLog.Offset)
Assert.Equal(maxSize, changeLog.MaxSize)
[<Property(Arbitrary=[|typeof<Generators.Generator>|])>]
let ``all changes in changeLog.stream should have UpdateType.Update`` (payloads: Payload array) =
let mutable changeLog = ChangeLog.create(5u)
for p in payloads do
changeLog <- ChangeLog.append(changeLog, p)
let result = changeLog.Stream.All(fun change -> change.Type = UpdateType.Update)
Assert.True(result)
TDD without Mocks
44. We have our own frontend
but
we need a Data
The Next Challenge
Oops?!
49. Applied at the heart of the system
Zero Null Reference exceptions
Domain errors instead of exceptions
DDD with types (strong determinism)
Dependency Rejection
TDD without mocks (property based testing)
50. let result = [ChaosMonkey
LatencyMonkey
CpuMonkey
MemoryMonkey]
|> induceDamage