Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: add more docs on integration with C libraries #153

Merged
merged 1 commit into from
Jul 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,14 @@ The Superposition platform comes with three components:
* **Experimentation** - a experimentation management system that allows supplying different configuration values to equal-sized cohorts (facilitating A/B testing)
* **Metrics** - a metrics sub-system that interacts with analytics backends to provide supporting metrics that enable conclusions to be drawn from experiments (TBD)

## Immediately get started with Superposition and a demo app

```
docker run -d -p 8081:9090 -p 8080:8080 datron1/superposition-demo-app:latest
```

Once you run this command, you'll find the demo app at `localhost:8081` and Superposition at `localhost:8080`. Play around to understand Superposition better, then dive into the documentation below!

## Detailed documentation
1. [Context-Aware-Configuration](docs/context-aware-config.md)
2. [Experimentation](docs/experimentation.md)
Expand All @@ -16,11 +24,12 @@ The Superposition platform comes with three components:
5. [Local setup](docs/setup.md)
6. [Context-Aware-Configuration API Ref - Postman Collection](postman/cac.postman_collection.json)
7. [Experimentation API Ref - Postman Collection](postman/experimentation-platform.postman_collection.json)
8. [Hitchiker's Guide to Create a New Client](docs/creating-client.md)

## Key highlights
* **Admin UI** - Out of the box administration (and tools) UI for configurations and experiments
* **Rich API support** - every action on the platform to manage configurations / experiments is supported with an accompanying API
* **Safe configuration changes** - support canary testing for releasing configuration changes using experiments
* **Type/Validation support** - Comprehensive type support using json-schema and custom validator function support for configuration values
* **Multi-tenant support** - a single deployment allows multiple tenants to manage their configurations/experiments in a completely isolated manner
* **Authn/Authz support** - control who can make configuration/experimentation changes
* **Authn/Authz support** - control who can make configuration/experimentation changes
231 changes: 231 additions & 0 deletions docs/creating-client.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,231 @@

- [Contributing Guidelines](#contributing-guidelines)
- [Context Aware Config](#context-aware-config)
- [Understanding the C structs and functions](#understanding-the-c-structs-and-functions)
- [struct Arc\_Client](#struct-arc_client)
- [int cac\_last\_error\_length(void)](#int-cac_last_error_lengthvoid)
- [const char \*cac\_last\_error\_message(void)](#const-char-cac_last_error_messagevoid)
- [void cac\_free\_string(char \*s)](#void-cac_free_stringchar-s)
- [int cac\_new\_client(const char \*tenant, unsigned long update\_frequency, const char \*hostname)](#int-cac_new_clientconst-char-tenant-unsigned-long-update_frequency-const-char-hostname)
- [void cac\_start\_polling\_update(const char \*tenant)](#void-cac_start_polling_updateconst-char-tenant)
- [void cac\_free\_client(struct Arc\_Client \*ptr)](#void-cac_free_clientstruct-arc_client-ptr)
- [struct Arc\_Client \*cac\_get\_client(const char \*tenant)](#struct-arc_client-cac_get_clientconst-char-tenant)
- [const char \*cac\_get\_last\_modified(struct Arc\_Client \*client)](#const-char-cac_get_last_modifiedstruct-arc_client-client)
- [const char \*cac\_get\_config(struct Arc\_Client \*client, const char \*filter\_query, const char \*filter\_prefix)](#const-char-cac_get_configstruct-arc_client-client-const-char-filter_query-const-char-filter_prefix)
- [const char \*cac\_get\_resolved\_config(struct Arc\_Client \*client, const char \*query, const char \*filter\_keys, const char \*merge\_strategy)](#const-char-cac_get_resolved_configstruct-arc_client-client-const-char-query-const-char-filter_keys-const-char-merge_strategy)
- [const char \*cac\_get\_default\_config(struct Arc\_Client \*client, const char \*filter\_keys)](#const-char-cac_get_default_configstruct-arc_client-client-const-char-filter_keys)
- [Testing with an Example](#testing-with-an-example)
- [Experimentation](#experimentation)
- [Understanding the C structs and functions](#understanding-the-c-structs-and-functions-1)
- [struct Arc\_Client](#struct-arc_client-1)
- [int expt\_last\_error\_length(void)](#int-expt_last_error_lengthvoid)
- [const char \*expt\_last\_error\_message(void)](#const-char-expt_last_error_messagevoid)
- [void expt\_free\_string(char \*s)](#void-expt_free_stringchar-s)
- [int expt\_new\_client(const char \*tenant, unsigned long update\_frequency, const char \*hostname)](#int-expt_new_clientconst-char-tenant-unsigned-long-update_frequency-const-char-hostname)
- [void expt\_start\_polling\_update(const char \*tenant)](#void-expt_start_polling_updateconst-char-tenant)
- [void expt\_free\_client(struct Arc\_Client \*ptr)](#void-expt_free_clientstruct-arc_client-ptr)
- [struct Arc\_Client \*expt\_get\_client(const char \*tenant)](#struct-arc_client-expt_get_clientconst-char-tenant)
- [char \*expt\_get\_applicable\_variant(struct Arc\_Client \*client, const char \*c\_context, short toss)](#char-expt_get_applicable_variantstruct-arc_client-client-const-char-c_context-short-toss)
- [char \*expt\_get\_satisfied\_experiments(struct Arc\_Client \*client, const char \*c\_context, const char \*filter\_prefix)](#char-expt_get_satisfied_experimentsstruct-arc_client-client-const-char-c_context-const-char-filter_prefix)
- [char \*expt\_get\_filtered\_satisfied\_experiments(struct Arc\_Client \*client, const char \*c\_context, const char \*filter\_prefix)](#char-expt_get_filtered_satisfied_experimentsstruct-arc_client-client-const-char-c_context-const-char-filter_prefix)
- [char \*expt\_get\_running\_experiments(struct Arc\_Client \*client)](#char-expt_get_running_experimentsstruct-arc_client-client)
- [Testing with an Example](#testing-with-an-example-1)

Superposition and her clients for CAC and Experimentation are written in rust. Since servers/programs implemented in a particular language need libraries written in their own language, we try to support as many languages as possible to make integrations easier. Some other reasons for providing language specific clients:

- Many languages have a paradigm like OOP or Functional and forcing them to adopt another paradigm may make it confusing to programmers
- Some engineers may get the implementation of the client wrong and may impact performance or memory usage
- It makes it faster to work with a client in a particular language

If you want to add support for a programming language, read on.

# Contributing Guidelines

Thank you for considering adding support for a new language in superposition clients! The following conditions must be met for your PR to be reviewed:

- Your client code should be formatted (You can mention the formatter used in the PR)
- Unit tests of your client code
- Documentation of your client functions is necessary ([See this for an example](client-context-aware-configuration.md))
- An example implementation of the client calling Superposition, loading the configurations and calling all exposed functions of your implemented client

# Context Aware Config

## Understanding the C structs and functions

All C structs and functions are generated automatically when the `cac-client` written in rust is compiled. Superposition uses the crate `cbindgen` to do this. The compiler generates a `.h` header file and an object file specific to the target platform, for example a `.dll` for windows or `.so` for linux. Some tips for writing an FFI/ABI to these files:

- All memory operations should be done by the rust segment of the code. You should always free memory for strings and clients using the inbuilt `free_*` functions
- You don't need to store the client on your implementation's end. Always use `get_cac_client` to get a pointer to a client to pass to other functions
- Remember, a client is created for a particular tenant
- Check the header files in the `headers` directory for further documentation
- Your client implementation should expect the object file that it dynamically links to be present in the same directory
- Try to use nix so that the process of dynamic linking dependencies becomes easier. You have to support non-nix setups as well

### struct Arc_Client

This is a pointer to `Arc<Client>` used by rust to manage asynchronous access to the `Client` struct. Most functions expect `Arc_Client` to know what client to operate on

### int cac_last_error_length(void)

Get the length of the error message that was produced during an operation.

Returns greater than 0 if an error has occurred
Returns 0 if no error

### const char *cac_last_error_message(void)

This function returns a character pointer to the last error message produced by an operation. Use `cac_last_error_length` to get its length to initialize a string if your language requires it.

returns null pointer if no error is present

### void cac_free_string(char *s)

This function takes a character pointer as an arg and frees memory allocated to it. Use it whenever you want to free a string returned by function of `cac-client` so that the rust side can continue to handle memory management, preventing any unwanted memory leaks

### int cac_new_client(const char *tenant, unsigned long update_frequency, const char *hostname)

A function that takes a tenant name as string, the update frequency and the hostname of the Superposition Server as arguments and creates a client that is internally managed by rust. Use `cac_get_client` to get a reference to this client

Returns 0 if client was successfully initialized
Returns 1 if an error occurred, use `cac_last_error_message` to get the error

### void cac_start_polling_update(const char *tenant)

Start polling the superposition server for updates for the given tenant

### void cac_free_client(struct Arc_Client *ptr)

Free the memory allocated to the cac client. Always call this function instead of the implementing language's `free` memory function

### struct Arc_Client *cac_get_client(const char *tenant)

A function to fetch a previously created client by using the tenant argument. Use `cac_new_client` to create a new client.

returns a null pointer if an error occurred. Use `cac_last_error_message` to get the error

returns a pointer to Arc_Client that can be used to perform other client operations

### const char *cac_get_last_modified(struct Arc_Client *client)

A function to get the last modified time of a tenant's config.

returns a null pointer if an error occurred. Use `cac_last_error_message` to get the error

returns a string that represents the last modified time of your tenant's configs

### const char *cac_get_config(struct Arc_Client *client, const char *filter_query, const char *filter_prefix)

A function that returns the config for your tenant. Takes the client, a context (filter_query) and a config prefix to filter on. All configs that have the prefix will be returned.

returns a null pointer if an error occurred. Use `cac_last_error_message` to get the error

returns a string that represents the config of your tenant based on your client and filters

### const char *cac_get_resolved_config(struct Arc_Client *client, const char *query, const char *filter_keys, const char *merge_strategy)

Does the same thing as `cac_get_config` but does not return the entire config, rather the config filtered on the keys provided as arguments

returns a null pointer if an error occurred. Use `cac_last_error_message` to get the error

returns a string that represents the resolved config of your tenant based on your client and filters

### const char *cac_get_default_config(struct Arc_Client *client, const char *filter_keys)

Does the same thing as `cac_get_config` but returns the entire default config, filtered on the config keys provided

returns a null pointer if an error occurred. Use `cac_last_error_message` to get the error

returns a string that represents the default config of your tenant based on your client and filters

## Testing with an Example

Checkout the examples directory to understand how to create examples for `cac_client_integration_example`

# Experimentation

## Understanding the C structs and functions

All C structs and functions are generated automatically when the `exp-client` written in rust is compiled. Superposition uses the crate `cbindgen` to do this. The compiler generates a `.h` header file and an object file specific to the target platform, for example a `.dll` for windows or `.so` for linux. Some tips for writing an FFI/ABI to these files:

- All memory operations should be done by the rust segment of the code. You should always free memory for strings and clients using the inbuilt `free_*` functions
- You don't need to store the client on your implementation's end. Always use `get_exp_client` to get a pointer to a client to pass to other functions
- Remember, a client is created for a particular tenant
- Check the header files in the `headers` directory for further documentation
- Your client implementation should expect the object file that it dynamically links to be present in the same directory
- Try to use nix so that the process of dynamic linking dependencies becomes easier. You have to support non-nix setups as well

### struct Arc_Client

This is a pointer to `Arc<Client>` used by rust to manage asynchronous access to the `Client` struct. Most functions expect `Arc_Client` to know what client to operate on

### int expt_last_error_length(void)

Get the length of the error message that was produced during an operation.

Returns greater than 0 if an error has occurred
Returns 0 if no error

### const char *expt_last_error_message(void)

This function returns a character pointer to the last error message produced by an operation. Use `cac_last_error_length` to get its length to initialize a string if your language requires it.

returns null pointer if no error is present

### void expt_free_string(char *s)

This function takes a character pointer as an arg and frees memory allocated to it. Use it whenever you want to free a string returned by function of `exp-client` so that the rust side can continue to handle memory management, preventing any unwanted memory leaks

### int expt_new_client(const char *tenant, unsigned long update_frequency, const char *hostname)

A function that takes a tenant name as string, the update frequency and the hostname of the Superposition Server as arguments and creates a client that is internally managed by rust. Use `expt_get_client` to get a reference to this client

Returns 0 if client was successfully initialized
Returns 1 if an error occurred, use `expt_last_error_message` to get the error

### void expt_start_polling_update(const char *tenant)

Start polling the superposition server for updates for the given tenant

### void expt_free_client(struct Arc_Client *ptr)

Free the memory allocated to the cac client. Always call this function instead of the implementing language's `free` memory function

### struct Arc_Client *expt_get_client(const char *tenant)

A function to fetch a previously created client by using the tenant argument. Use `expt_new_client` to create a new client.

returns a null pointer if an error occurred. Use `expt_last_error_message` to get the error

returns a pointer to Arc_Client that can be used to perform other client operations

### char *expt_get_applicable_variant(struct Arc_Client *client, const char *c_context, short toss)

get the experiments that apply to a given context `c_context`. It also takes a number toss between 0 - 100 that is used to assign a variant IDs

returns null pointer if no variant is found
returns a string formatted array of variant IDs that match the parameters passed

### char *expt_get_satisfied_experiments(struct Arc_Client *client, const char *c_context, const char *filter_prefix)

get the experiments that apply to a given context `c_context`. It also filters on config key prefix

returns null pointer if no variant is found
returns a string formatted array of experiments that match the parameters passed

### char *expt_get_filtered_satisfied_experiments(struct Arc_Client *client, const char *c_context, const char *filter_prefix)

get the experiments that apply to a given context `c_context`. It also filters on config key prefix

returns null pointer if no variant is found
returns a string formatted array of experiments that match the parameters passed

### char *expt_get_running_experiments(struct Arc_Client *client)

get all currently running experiments

returns null pointer if no variant is found
returns a string formatted array of experiments that match the parameters passed

## Testing with an Example

Checkout the examples directory to understand how to create examples for `exp_client_integration_example`
Loading