Eppo is a modular flagging and experimentation analysis tool. Eppo's .NET SDK is built to make assignments in multi-user server side contexts, compatible with Dot Net 7.0 Runtime. Before proceeding you'll need an Eppo account.
Despite the name, this SDK runs in both server and client applications.
- Feature gates
- Kill switches
- Progressive rollouts
- A/B/n experiments
- Mutually exclusive experiments (Layers)
- Dynamic configuration
- Contextual Multi-Armed Bandits
In your .NET application, add the Eppo.Sdk Package from Nuget.
dotnet add package Eppo.Sdk
In order to have the most up-to-date Assignment and Bandit configuration, the SDK frequently polls the configuration server. This is not ideal in a client deployment such as a mobile or web app. For these client applications, this SDK can be run in Client Mode where configuration loading is done once on InitClientMode
and on-demand by calling the EppoClient.RefreshConfiguration
method.
Begin by initializing a singleton instance of Eppo's client. Once initialized, the client can be used to make assignments anywhere in your app.
var eppoClientConfig = new EppoClientConfig('SDK-KEY-FROM-DASHBOARD');
// For servers; immediately loads configuration and every roughly, 30 seconds after:
var eppoClient = EppoClient.Init(eppoClientConfig);
// For client (such as mobile) applications; initializes and immediately loads configuration once:
var eppoClient = EppoClient.InitClientMode(eppoClientConfig);
// Client applications, On app reload or other trigger to reload the configuration.
eppoClient.RefreshConfiguration();
var assignedVariation = eppoClient.GetStringAssignment(
'new-user-onboarding',
user.id,
user.attributes,
'control'
);
This SDK supports Multi-armed Contextual Bandits.
var subjectAttributes = new Dictionary<string, object?>()
{
["age"] = 30, // Gets interpreted as a Numeric Attribute
["country"] = "uk", // Categorical Attribute
["pricingTier"] = "1" // NOTE: Deliberately setting to string causes this to be treated as a Categorical Attribute
};
var actions = new Dictionary<string, IDictionary<string, object?>>()
{
["nike"] = new Dictionary<string, object?>()
{
["brandLoyalty"] = 0.4,
["from"] = "usa"
},
["adidas"] = new Dictionary<string, object?>()
{
["brandLoyalty"] = 2,
["from"] = "germany"
}
};
var result = client.GetBanditAction(
"flagKey",
"subjecKey",
subjectAttributes,
actions,
"defaultValue");
if (result.Action != null)
{
// Follow the Bandit action
DoAction(result.Action);
} else {
// User was not selected for a Bandit.
// A variation is still assigned.
DoSomething(result.Variation);
}
Every Eppo flag has a return type that is set once on creation in the dashboard. Once a flag is created, assignments in code should be made using the corresponding typed function:
GetBooleanAssignment(...)
GetNumericAssignment(...)
GetIntegerAssignment(...)
GetStringAssignment(...)
GetJSONAssignment(...)
Each function has the same signature, but returns the type in the function name. For booleans use getBooleanAssignment
, which has the following signature:
public bool GetBooleanAssignment(
string flagKey,
string subjectKey,
Dictionary<string, object> subjectAttributes,
string defaultValue
)
The Init
and InitClient
functions accept the following optional configuration arguments.
Option | Type | Description | Default |
---|---|---|---|
assignmentLogger |
AssignmentLogger | A callback that sends each assignment to your data warehouse. Required only for experiment analysis. See example below. | None |
For additional control in server deployments, the EppoClientConfig
class can be initialized with a custom interval to override the default of 30sec.
var config = new EppoClientConfig("YOUR-API-KEY", myAssignmentLogger)
{
PollingIntervalInMillis = 5000
};
To use the Eppo SDK for experiments that require analysis, pass in a callback logging function to the init
function on SDK initialization. The SDK invokes the callback to capture assignment data whenever a variation is assigned or a Bandit Action is selected. The assignment data is needed in the warehouse to perform analysis.
The code below illustrates an example implementation of a logging callback using Segment. You could also use your own logging system, the only requirement is that the SDK receives a LogAssignment
and a LogBanditAction
function. Here we define an implementation of the Eppo IAssignmentLogger
interface:
class SegmentLogger : IAssignmentLogger
{
private readonly Analytics analytics;
public SegmentLogger(Analytics analytics)
{
this.analytics = analytics;
}
public void LogAssignment(AssignmentLogData assignmentLogData)
{
analytics.Track("Eppo Randomization Assignment", assignmentLogData);
}
public void LogBanditAction(BanditLogEvent banditLogEvent)
{
analytics.Track("Eppo Bandit Action", banditLogEvent);
}
}
class Program
{
public void main()
{
// Initialize Segment and Eppo clients.
var segmentConfig = new Configuration(
"<YOUR WRITE KEY>",
flushAt: 20,
flushInterval: 30);
var analytics = new Analytics(segmentConfig);
// Create a logger to send data back to the Segment data warehouse
var logger = new SegmentLogger(analytics);
// Initialize the Eppo Client
var eppoClientConfig = new EppoClientConfig("EPPO-SDK-KEY-FROM-DASHBOARD", logger);
var eppoClient = EppoClient.Init(eppoClientConfig);
// Elsewhere in your code, typically just after the user logs in.
var subjectTraits = new JsonObject()
{
["email"] = "janedoe@liamg.com",
["age"] = 35,
["accountAge"] = 2,
["tier"] = "gold"
}; // User properties will come from your database/login service etc.
var userID = "user-123";
// Identify the user in Segment Analytics.
analytics.Identify(userID, subjectTraits);
// Need to reformat user attributes a bit; EppoClient requires `IDictionary<string, object?>`
var subjectAttributes = subjectTraits.Keys.ToDictionary(key => key, key => (object)subjectTraits[key]);
// Get an assignment for the user
var assignedVariation = eppoClient.GetStringAssignment(
"new-user-onboarding",
userID,
subjectAttributes,
"control"
);
}
}
class SegmentLogger : IAssignmentLogger
{
private readonly Analytics analytics;
public SegmentLogger(Analytics analytics)
{
this.analytics = analytics;
}
public void LogAssignment(AssignmentLogData assignmentLogData)
{
analytics.Track("Eppo Randomization Assignment", assignmentLogData);
}
public void LogBanditAction(BanditLogEvent banditLogEvent)
{
analytics.Track("Eppo Bandit Action", banditLogEvent);
}
}
Eppo's SDKs are built for simplicity, speed and reliability. Flag configurations are compressed and distributed over a global CDN (Fastly), typically reaching your servers in under 15ms. Server SDKs continue polling Eppo’s API at 30-second intervals. Configurations are then cached locally, ensuring that each assignment is made instantly. Evaluation logic within each SDK consists of a few lines of simple numeric and string comparisons. The typed functions listed above are all developers need to understand, abstracting away the complexity of the Eppo's underlying (and expanding) feature set.
- Download dotnet 7.0 installer to have access to the cli and runtimes
- Download dotnet binary and copy it to your $PATH
Expected environment:
✗ dotnet --list-sdks
7.0.406
✗ dotnet --list-runtimes
Microsoft.AspNetCore.App 7.0.16
Microsoft.NETCore.App 7.0.16