{"id":21097918,"url":"https://github.com/dehora/outland","last_synced_at":"2025-09-07T10:31:13.421Z","repository":{"id":66896245,"uuid":"83838306","full_name":"dehora/outland","owner":"dehora","description":"Proof of concept for a distributed feature flag system","archived":false,"fork":false,"pushed_at":"2017-07-13T23:24:42.000Z","size":944,"stargazers_count":5,"open_issues_count":0,"forks_count":1,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-05-16T16:49:23.302Z","etag":null,"topics":["dropwizard","dynamodb","gradle","hybrid-logical-clocks","hystrix","java","protocol-buffers","redis","ulid"],"latest_commit_sha":null,"homepage":"","language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/dehora.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGES.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2017-03-03T20:27:12.000Z","updated_at":"2019-04-16T12:20:52.000Z","dependencies_parsed_at":"2023-02-22T16:45:17.868Z","dependency_job_id":null,"html_url":"https://github.com/dehora/outland","commit_stats":null,"previous_names":[],"tags_count":8,"template":false,"template_full_name":null,"purl":"pkg:github/dehora/outland","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dehora%2Foutland","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dehora%2Foutland/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dehora%2Foutland/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dehora%2Foutland/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/dehora","download_url":"https://codeload.github.com/dehora/outland/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dehora%2Foutland/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":274026687,"owners_count":25209739,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-09-07T02:00:09.463Z","response_time":67,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["dropwizard","dynamodb","gradle","hybrid-logical-clocks","hystrix","java","protocol-buffers","redis","ulid"],"created_at":"2024-11-19T22:52:01.968Z","updated_at":"2025-09-07T10:31:13.383Z","avatar_url":"https://github.com/dehora.png","language":"Java","funding_links":[],"categories":[],"sub_categories":[],"readme":"\n**Status**\n\n- Build: [![CircleCI](https://circleci.com/gh/dehora/outland.svg?style=svg)](https://circleci.com/gh/dehora/outland)\n- Client Download: [ ![Download](https://api.bintray.com/packages/dehora/maven/outland-feature-java/images/download.svg) ](https://bintray.com/dehora/maven/outland-feature-java/_latestVersion)\n- Server Download: [ ![Download](https://api.bintray.com/packages/dehora/maven/outland-feature-server/images/download.svg) ](https://bintray.com/dehora/maven/outland-feature-server/_latestVersion)\n- Source Release: [0.0.12](https://github.com/dehora/outland/releases/tag/0.0.12)\n- Contact: [maintainers](https://github.com/dehora/outland/blob/master/MAINTAINERS)\n\n# Outland\n\n\u003c!-- START doctoc generated TOC please keep comment here to allow auto update --\u003e\n\u003c!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE --\u003e\n**Table of Contents**  *generated with [DocToc](https://github.com/thlorenz/doctoc)*\n\n- [Welcome to Outland](#welcome-to-outland)\n  - [Why Feature Flags?](#why-feature-flags)\n  - [Why a Service?](#why-a-service)\n  - [Project Status](#project-status)\n- [Quickstart](#quickstart)\n  - [Server](#server)\n    - [Start a Server with Docker](#start-a-server-with-docker)\n    - [Create a Group and some Features via the API](#create-a-group-and-some-features-via-the-api)\n    - [Enable a Feature](#enable-a-feature)\n    - [Add a Feature Namespace](#add-a-feature-namespace)\n  - [Client](#client)\n    - [Add the client library](#add-the-client-library)\n    - [Evaluate a Feature](#evaluate-a-feature)\n    - [Client Feature API](#client-feature-api)\n- [Outland Feature Flag Model](#outland-feature-flag-model)\n  - [Summary: Features, Groups and Namespaces](#summary-features-groups-and-namespaces)\n  - [Features](#features)\n  - [Feature Flags and Feature Options](#feature-flags-and-feature-options)\n    - [Option Selection](#option-selection)\n    - [Boolean Options](#boolean-options)\n    - [String Options](#string-options)\n    - [Option Controls](#option-controls)\n  - [Groups](#groups)\n    - [Group Access Control](#group-access-control)\n  - [Namespaces](#namespaces)\n    - [Pattern: using namespaces for environments](#pattern-using-namespaces-for-environments)\n- [Installation](#installation)\n  - [Server](#server-1)\n    - [Docker](#docker)\n    - [Creating Tables in DynamoDB](#creating-tables-in-dynamodb)\n    - [Creating a sample Group and Features](#creating-a-sample-group-and-features)\n    - [Configuring the Server](#configuring-the-server)\n    - [Server API Authentication](#server-api-authentication)\n  - [Client Installation](#client-installation)\n    - [Maven](#maven)\n    - [Gradle](#gradle)\n    - [SBT](#sbt)\n- [Build and Development](#build-and-development)\n- [Contributing](#contributing)\n- [License](#license)\n\n\u003c!-- END doctoc generated TOC please keep comment here to allow auto update --\u003e\n\n\n# Welcome to Outland\n\nOutland is distributed feature flag and event messaging system.\n\nThe reason Outland exists is the notion that feature flags are a first class engineering and \nproduct development activity that let you work smaller, better, faster, and with less risk. \n\nOutland consists of an API server and a Java client, with the ambition to support an admin UI, \nclients in other languages, a decentralised cluster mode, a container sidecar, and more advanced \nevaluation and tracking options. \n\n## Why Feature Flags?\n\nFeature flagging offers significant leverage and shouldn't just be a technology bolt-on or an \nafterthought, as is often the case today.\n\nFeature flags allow you to:\n\n- Work safely. Turning a dynamic flag off in production is faster than a rollback. \n\n- Ship larger scale functionality faster as a set of small steps.  \n\n- Avoid long lived feature branches with their merge and integration test overhead. \n\nFlags reinforce the benefits you get from shipping small changes and continuous delivery,  \nproviding a flywheel effect for those practices.\n\nYou can read more about the concepts in the post [\"Feature Flags: Smaller, Better, Faster Software Development\"](https://medium.com/@dehora/feature-flags-smaller-better-faster-software-development-f2eab58df0f9)\n\n## Why a Service?\n\nWe can identify four reasons to make feature flagging a service:\n\n- **Everyone solves this differently**: Flag systems are typically built with whatever's to hand \ninstead of a system that designed from the ground up to support flag based engineering and development.  \n\n- **Support product development**: Feature flags have a way of becoming important to product and \nservice development processes over time. Having a first class service makes this easier \nand simplifies coordination delivery across services and teams. \n\n- **Observability**: In a microservices world (and also for monolithic or monocentric systems) \nthere's value in making flag state observable via a service. Service access \nto feature flags allows human operators to more easily intervene and control systems change.\n\n- **Incident management**: A point of flags is to allow features to be turned off quickly. If the \nflag system is a internal bolt-on to whatever the team happens to run is far more risky than a \nservice interface that offers the least power needed to change the state. \n\n## Project Status\n\nOutland is not production ready.\n\nThe client and server are pre 1.0.0, with the aim of getting to a usable state soon (April 2017). \nThe admin UI and cluster mode are next in line. See also:\n\n- [Trello Roadmap](http://bit.ly/2nje8ou) is where the project direction is written down.\n- [Open Issues](http://bit.ly/2nLZNUT) section has a  list of bugs and things to get done. \n- [Help Wanted](http://bit.ly/2ngXkxP) has a list of things that would be nice to have.\n\n# Quickstart\n\nClone the project from github:\n \n```bash\ngit clone git@github.com:dehora/outland.git\n```\n\n## Server\n\n### Start a Server with Docker\n\nYou can use the docker setup in [examples/quickstart](https://github.com/dehora/outland/tree/master/outland-feature-docker/examples/quickstart) \nto get an outland feature server running.\n\nGo to the `examples/quickstart` directory, \n\n```bash\ncd outland/outland-feature-docker/examples/quickstart\n```\n\nrun the `start_outland` script:\n\n```bash\n./start_outland\n```\n\nThis will:\n\n - Add the redis and dynamodb-local images and start them.\n - Start an Outland container on port 8180.\n - Create the dynamodb tables used by the server.\n - Seed the server with a group called `testgroup`.\n - Add three feature flags to the group, `test-flag-1`, `test-option-1` and `test-flag-namespace-1`. \n \n \nYou can see the group's list of features via the API:\n\n```bash\ncurl -v http://localhost:8180/features/testgroup -u testconsole/service:letmein\n```\n\nAs well as the Group itself and its grants:\n\n```bash\ncurl -v http://localhost:8180/groups/testgroup -u testconsole/service:letmein\n```\n\nDummy credentials are setup as a convenience in the folder's `.env` file, this is how the \n`testconsole` service is given access (all API calls require authentication). \n\n### Create a Group and some Features via the API\n\nA _group_ contains one or more features and describe which services or teams are \ngranted access to those features. Every group also has one or more _owners_.\n\nThe example below creates a group called testgroup-1 that can be accessed by a service called \n`testservice-1` and a member called `testuser-1` and whose owner is \nalso `testuser-1`:\n```bash\ncurl -v -XPOST http://localhost:8180/groups   \\\n-H \"Content-type: application/json\" \\\n-u testconsole/service:letmein  -d'\n{\n  \"key\": \"testgroup-1\"\n  ,\"name\": \"Test Group One\"\n  ,\"owners\": {\n    \"items\":[{\n      \"name\": \"Test User One\"\n      ,\"username\": \"testuser-1\"\n      ,\"email\": \"testuser-1@example.org\"\n      }\n     ]\n   }\n  ,\"granted\" : {\n    \"services\":[{\n      \"key\": \"testservice-1\"\n      ,\"name\": \"Test Service One\"\n    }]\n    ,\"members\":[{\n      \"username\": \"testuser-1\"\n      ,\"name\": \"Test User One\"\n    }]\n  }\n}\n'\n```\n\nOwners have permissions to edit the group, but owners are not granted access to the group's \nfeatures by default - only services and members are allowed to access features. \n\n\nLet's add a _feature_ to the group by posting the feature JSON to the server associating it with \nthe group's `key`. This one is a simple on/off flag:\n\n```bash\ncurl -v http://localhost:8180/features/testgroup-1 \\\n-H \"Content-type: application/json\" \\\n-u testconsole/service:letmein -d'\n{\n  \"key\": \"testfeature-1\"\n  ,\"description\": \"A test feature flag\"\n  ,\"group\": \"testgroup-1\"\n  ,\"options\": {\n    \"option\": \"flag\"\n  },\n  \"owner\": {\n    \"name\": \"Test User One\"\n    ,\"username\": \"testuser-1\"\n  }\n}\n'\n```\n\nWe can also add a feature _option_. These are features that give each possible result a weight, \nallowing you to fire a feature to just a percentage of requests. This is useful for canary deployments. \n\nThis example creates a `bool` feature which has just two options, true and false. \nThe options are weighted so that the feature is false 95% of the time.\n\n```bash\ncurl -v http://localhost:8180/features \\\n-H \"Content-type: application/json\" \\\n-u testconsole/service:letmein -d'\n{\n  \"key\": \"testfeature-2\"\n  ,\"description\": \"A test feature flag\"\n  ,\"group\": \"testgroup-1\"\n  ,\"options\": {\n    \"option\": \"bool\",\n    \"items\":[\n      {\n        \"key\":\"false\",\n        \"value\":\"false\",\n        \"weight\": 9500\n      },\n      {\n        \"key\": \"true\",\n        \"value\": \"true\",\n        \"weight\": 500\n      }\n    ]\n  },\n  \"owner\": {\n    \"name\": \"Test User One\"\n    ,\"username\": \"testuser-1\"\n  }\n}\n'\n```\n\n### Enable a Feature\n\nFeatures are off by default. Let's enable the first feature `testfeature-1` by setting its state \nto on:\n\n```bash\ncurl -v -XPOST  http://localhost:8180/features/testgroup-1/testfeature-1 \\\n-H \"Content-type: application/json\" \\\n-u testconsole/service:letmein -d'\n{\n  \"key\": \"testfeature-1\"\n  ,\"group\": \"testgroup-1\"\n  ,\"status\": \"on\"\n}  \n'\n```\n\n### Add a Feature Namespace\n\nFeatures can have multiple namespaces allowing you define a variation of the feature for a \nparticular context, such as an environment. Let's add a \"production\" namespace to the first \nfeature `testfeature-1`: \n\n```bash\ncurl -v -XPOST  http://localhost:8180/features/testgroup-1/testfeature-1/namespaces \\\n-H \"Content-type: application/json\" \\\n-u testconsole/service:letmein -d'\n{\n  \"namespace\": \"production\",\n  \"feature\": {\n    \"key\": \"testfeature-1\"\n  }\n}\n'\n```\n\n## Client\n\n### Add the client library\n\nThe client is available via JCenter, see the [Client](#client) section for details. \n\nOnce the client is setup up as a dependency you can configure it as follows:\n\n```java\n  ServerConfiguration conf = new ServerConfiguration()\n      .baseURI(\"http://localhost:8180\")\n      .defaultGroup(\"testgroup-1\");\n\n  FeatureClient client = FeatureClient.newBuilder()\n      .serverConfiguration(conf)\n      .authorizationProvider(\n          (group, scope) -\u003e Optional.of(new Authorization(Authorization.REALM_BASIC,\n              new String(Base64.getEncoder().encode((\"testconsole/service:letmein\").getBytes())))))\n      .build();   \n```\n\nIn a real world setting the `authorizationProvider` would not be hardcoded with credentials, but \nthis will do to connect to the local server. \n\n### Evaluate a Feature\n\nNow you can check one the features created by the seed script:\n\n```java\n  if (client.enabled(\"testfeature-1\")) {\n    System.out.println(featureKey+\": enabled\");\n  } else {\n    System.out.println(featureKey+\": disabled\");\n  }\n```\n\nTo access a feature in a particular group you can use the extended form of `enabled`:\n\n```java\n  if (client.enabled(\"testgroup-1\", \"testfeature-1\")) {\n    System.out.println(featureKey+\": enabled\");\n  } else {\n    System.out.println(featureKey+\": disabled\");\n  }\n```\n\nIt's common to work with just one group so the short form exists as a handy convenience. The \n short form takes its group from the `defaultGroup` set via `ServerConfiguration`.\n\n\n### Client Feature API\n\nYou can manage features via the client via `FeatureResource`:\n\n```java\n  FeatureResource features = client.resources().features();\n\n  Feature feature = Feature.newBuilder()\n      .setGroup(\"testgroup-1\")\n      .setKey(\"testfeature-1\")\n      .setStatus(Feature.Status.on)\n      .build();\n\n  Feature updated = features.update(feature);\n  System.out.println(\"test-flag-1 state: \" + updated.getStatus());\n```\n\n\n# Outland Feature Flag Model\n\n## Summary: Features, Groups and Namespaces\n\nA _Feature_ is identified by a _key_, and can be in an _on_ or _off_ state. Every feature has an owner\nand a version.  As well as a state, Features may also have a set of _options_. These are evaluated \nin the on state and one option is returned. Each option can have a weight that affects the chance \nif it being returned.\n\nA _Group_ is a collection of features and also identified by a key. Every Group at \nleast one owner. A Feature always belongs to one Group. Groups also hold a list of services \nand team members that are allowed access its features. \n\nA feature can have optionally have one or more _Namespaces_ that carry a custom variation of\nthe feature's state. For example a feature can be disabled by default but enabled for a \nnamespace called `staging`.\n\n## Features\n\nFeatures have a state, which can be `on` or `off` indicating whether the feature is enabled or \ndisabled. As well as its state, a feature has a _key_ acting as an identifier that allows it to be \nchecked via the client, for example -\n\n```java\nString featureKey = \"my-new-feature\";\nif (features.enabled(featureKey)) {\n  System.out.println(\"New feature!\");\n} else {\n  System.out.println(\"Old feature!\");\n}\n```\n \nA feature has also a description, an owner and a version. Features have an owner \nbecause we've found it helpful to be able to get in touch with someone about the feature, even \nwhere there is a description for it. Versions are useful for tracking changes and in Outland \nversion identifiers are also orderable.\n\n## Feature Flags and Feature Options\n\nFeatures have two forms - _flags_ and _options_. Features which are `on` are _evaluated_ and \nfeatures which are `off` are considered disabled and short circuited. What happens during \nevaluation depends on the kind of feature.\n  \nThe `Flag` is the simplest kind of feature and probably the one you're most familiar with. When a \nflag is on it evaluates to true and you're good to go. \n  \nAn `Option` is more involved and has two stages:\n\n- First, the `on` or `off` state is checked to see if it's enabled, and if the state is `off` it's skipped just like a Flag. \n\n- Second, if the state `on`, the feature's available options are evaluated and one of them is selected. \n\nA Flag can be considered a reductive form of Option, where the weight of a Flag is wholly allocated \nto its state. But Flags are such a common case we work with them using their state directly \nand use the Option form for more advanced scenarios.   \n\n### Option Selection\n\nEach option has a _weight_ and the probability of an option being selected is proportional to its weight. \n\nLet's take a boolean feature option as an example. A boolean feature will have two options, \"true\" \nand \"false\". Now suppose \"false\" had a weight of 90%, and \"true\" had a weight of 10%. \nThen the feature is processed roughly like this:\n\n- If the state is `off`, the selection is skipped and the control value is returned.\n- If the state is `on`, the \"true\" and \"false\" options are evaluated.\n- 9 times out of 10 the evaluation will select \"false\".\n- 1 time out of 10 the evaluation will select \"true\".\n\nThis is sometimes called [\"roulette wheel selection\"](https://en.wikipedia.org/wiki/Fitness_proportionate_selection) and is how the client decides which option to return.\n\nThe process of returning an option is called \"selection\". Note that this is different to \ndetermining if a feature is enabled. In the client the distinction is made by using \n`enabled` calls to check feature state and `select` calls to return an option value:\n\n```java\n// select returns one of the option values\nString selection = client.select(\"colors\");\n\n// but enabled just checks to see if the feature is on:\nboolean on = client.enabled(\"colors\");\n```\n\n### Boolean Options\n\nA _boolean_ feature  option can only have true and false options, each of which can be given a weight. This makes the boolean option ideal for scenarios like canary rollouts where a \ncontrolled percentage of traffic is sent to the new code. As we see things going well, we can increase the weight of the \"true\" option allowing more requests to hit it. If it's not working \nout we can increase the \"false\" weight, biasing traffic away from the new feature. Worst case \nif it's a bust we can back out by setting the feature's state to `off` and disabling the \nfeature altogether. \n\nWeights can be entirely allocated to one of the boolean options. For example you can give \nthe \"true\" option all the weight by setting the \"false\" option to 0, as shown in this \nJSON fragment:\n\n```json\n{\n  \"key\": \"bool-weighted-all-true\",\n  \"group\": \"group1\",\n  \"status\": \"on\",\n  \"description\": \"A test feature option\",\n  \"options\": {\n    \"option\": \"bool\",\n    \"maxweight\": 10000,\n    \"items\": [\n      {\n        \"key\": \"true\",\n        \"value\": \"true\",\n        \"weight\": 10000\n      },\n      {\n        \"key\": \"false\",\n        \"value\": \"false\",\n        \"weight\": 0\n      }\n    ],\n    \"control\": \"false\"\n  }\n}\n```\n\nWhen these weights are evaluated the true option will always be returned. \n\n### String Options\n\nA string feature can have multiple options, again each of which can be given a weight. For \nexample a \"color\" feature could have 3 options, \"red\", \"green\" and \"blue\", each with a weight, biasing their selection to 10%, 20%, and 70% such that one time in ten the \"red\" option will be returned, two times out of ten it'll be \"green\", and seven times out of ten it'll be \"blue\".  \nThis JSON fragment shows what that would look like:\n\n```json\n{\n  \"key\": \"colors\",\n  \"group\": \"group1\",\n  \"status\": \"on\",\n  \"description\": \"A test feature string\",\n  \"options\": {\n    \"option\": \"string\",\n    \"maxweight\": 10000,\n    \"items\": [\n      {\n        \"key\": \"option-blue\",\n        \"value\": \"blue\",\n        \"weight\": 7000\n      },\n      {\n        \"key\": \"option-green\",\n        \"value\": \"green\",\n        \"weight\": 2000\n      },\n      {\n        \"key\": \"option-red\",\n        \"value\": \"red\",\n        \"weight\": 1000\n      }\n    ],\n    \"control\": \"option-green\"\n  }\n}\n```\n\nThis gives us a path beyond on/off toggles to things like A/B testing and multi-armed bandits.\n\nLike boolean options string features can be given weights and one string can be allocated all \nthe weight. Again as with booleans a string feature can have a `\"control\"` field indicating \nwhich option should be selected if the feature is off. In fact, the boolean option can \nbe considered a special case of a string option that is understood by the server and client.\n\n### Option Controls\n\nNote the last example has a field called `\"control\"` that is set to the `\"option-green\"`.  A  control defines which option should be returned if the feature is set to `off` - when the \nfeature is off the selection algorithm will return the option named by the control. If \nthe control is not declared the return value will default to an empty string in the \nclient. \n\nControls are useful as fallbacks and for test experiment scenarios such as A/B tests \ncan act as the value shown to the group outside the experiment.\n\nSometimes you want the control option to be different from the selection candidates. The \neasiest way to do that is to define an option whose weight is 0. This means it will never \nbe selected when the feature is enabled, but is available for use as the control option when \nthe feature is disabled.\n\n## Groups\n\nA Group is a collection of features and every feature belongs to just one Group. Every \nfeature in a Group must have a unique key within the Group. \n\nThe main goal of a Group is to allow features to be observed by multiple systems. For example \nyou might use a Group to group features together for an epic project such that it allows \nthose features to span multiple microservices owned by a few different teams. Or you may simply \nwant to make some features available to your backend server and your single page webapp.  \n\nOur experience is that the thing you want to develop often has multiple parts and often will \nspan multiple systems. It's inevitable some thing you want to build will cut across whatever \nservice boundaries you have in place, however well-considered they are. Groups allow you to \nhandle that and deal with requirements that are naturally divergent. \n\nEvery Group has one or more owners that can administrate the Group and act as point of \ncontact. \n \nFinally, the Group construct enables multi-tenancy, allowing multiple teams to share the \nsame Outland service. There's nothing to stop you running multiple Outland servers but it \ncan be nice to leverage shared infrastructure and reduce heavy lifting.\n\n### Group Access Control\n\nAs well as grouping features, an Group can _grant_ access to one or more services \n(typically running systems), or to one or members (typically individual or teams). Grants allow \nthe Group owner to declare which services can see the Group's features.  We'll just refer \nto both kinds as services for now but they are handled separately. \n\nOnce the service requesting access to a feature or group is authenticated it is checked to see \nif it's in the grant list for the Group. If it is it has access to the feature state, \notherwise it won't be authorised. \n\nOwners and grants are distinct - owners are not automatically given grants and are not looked \nup during authentication. \n\n## Namespaces\n\nEach feature can have one or more namespaces that contain variations of the feature state. The \nAPI returns the namespace variations as part of the feature's response data.\n\nThe client can be configured to use a particular namespace via its `ServerConfiguration`, \nfor example: \n\n```java\n  ServerConfiguration conf = new ServerConfiguration()\n      .baseURI(\"http://localhost:8180\")\n      .namespace(\"staging\");\n\n  FeatureClient client = ...;\n```\n\nIf a feature it's evaluating doesn't have that namespace the client will fall back to using \nthe feature's default state.\n\nYou can define feature namespaces when creating a feature, but they are easy to add to a feature after its been created by sending one to the features `namespaces` resource. Here's an example:\n\n```bash\ncurl -v http://localhost:8180/features/testgroup-1/testfeature-2/namespaces \\\n-H \"Content-type: application/json\" \\\n-u testconsole/service:letmein -d'\n{\n  \"namespace\": \"staging\",\n  \"feature\": {\n    \"key\": \"testfeature-2\",\n    \"status\": \"off\",\n    \"options\": {\n      \"option\": \"bool\",\n      \"items\": [\n        {\n          \"key\": \"false\",\n          \"value\": \"false\",\n          \"weight\": 9900\n        },\n        {\n          \"key\": \"true\",\n          \"value\": \"true\",\n          \"weight\": 100\n        }\n      ]\n    }\n  }\n}\n'\n```\n\n### Pattern: using namespaces for environments\n\nA common use of namespaces is to define per-environment settings. For example a \"production\" \nnamespace can have a bool option with a 99% false weight and a 1% true weight, whereas the \ndefault could be 90% false and 10% true. Using namespaces like this means you don't have to \ndefine a Group per environment you're working with.\n\n\n# Installation\n\n## Server\n\n### Docker\n\nThe server is available on [docker hub](https://hub.docker.com/r/dehora/outland-feature-server/).\n\nThe [examples/all-in-one](https://github.com/dehora/outland/tree/master/outland-feature-docker/examples/all-in-one) \nproject has a simple `docker-compose` file you can use to get started, which includes the \nDynamoDB and Redis dependencies along with some dummy credentials (stored in the docker compose `.env` file).\n\n### Creating Tables in DynamoDB\n\nOnce the server is up and running, for local development you can create the DynamoDB tables \nused to store feature data via the Dropwizard admin port. \n\nThe `create_tables` script in the [examples/all-in-one](https://github.com/dehora/outland/tree/master/outland-feature-docker/examples/all-in-one) directory will create the DynamoDB tables used by the server (if you run this a second time, the tables will not be recreated and server will return 500 responses).\n\nFor online or production use, you can create the tables via the AWS Console and choose to change their names as described [here](https://github.com/dehora/outland/blob/master/outland-feature-docker/README.md).\n\n### Creating a sample Group and Features\n\nThe `create_seed_group` will create a Group called `test-acme-group` with two example features. The script contains plain curl requests which you might find useful for seeing how to call the API. You can see the Group's list of features via the API:\n\n```sh\ncurl -v http://localhost:8180/features/test-acme-group -u testconsole/service:letmein\n```\n\nAs well as the Group itself and its grants:\n\n```sh\ncurl -v http://localhost:8180/groups/test-acme-group -u testconsole/service:letmein\n```\n\n### Configuring the Server\n\nThe docker image embeds its own Dropwizard configuration file to avoid requiring a mount. The \nsettings can be overridden by passing them to the environment. The full list of configuration \nsettings is available  [here](https://github.com/dehora/outland/blob/master/outland-feature-docker/README.md) - at minimum you'll want to set up authorization options for real world use as discussed there.\n\n### Server API Authentication\n\nThere's two options for authentication: \n\n- Basic Authentication: An \"API Key\" style model where callers have well known credentials sent \nin the password part along with their identity. This is enabled by default but no keys are configured.\n\n- OAuth Bearer Authentication: Callers submit bearer tokens and those tokens are verified by a \nremote OAuth server and exchanged for an OAuth token object that contains principal identity \nand scopes. This is disabled by default.\n\nThe options aren't mutually exclusive, you can enable both. \n\nThere's no way to turn authentication off, which is deliberate. Also, an unconfigured \nOutland server will fail to authenticate requests, ie, it won't work out of the box unless you \neither supply API Keys for the Basic option or enable access to an OAuth server to verify Bearer \ntokens.\n\n## Client Installation\n\n### Maven\n\nAdd jcenter to the repositories element in `pom.xml` or `settings.xml`:\n\n```xml\n\u003crepositories\u003e\n  \u003crepository\u003e\n    \u003cid\u003ejcenter\u003c/id\u003e\n    \u003curl\u003ehttp://jcenter.bintray.com\u003c/url\u003e\n  \u003c/repository\u003e\n\u003c/repositories\u003e\n```\n\nand add the project declaration to `pom.xml`:\n\n```xml\n\u003cdependency\u003e\n  \u003cgroupId\u003enet.dehora.outland\u003c/groupId\u003e\n  \u003cartifactId\u003eoutland-feature-java\u003c/artifactId\u003e\n  \u003cversion\u003e0.0.12\u003c/version\u003e\n\u003c/dependency\u003e\n```\n\n### Gradle\n\nAdd jcenter to the `repositories` block:\n\n```groovy\nrepositories {\n jcenter()\n}\n```\n\nand add the project to the `dependencies` block in `build.gradle`:\n\n```groovy\ndependencies {\n  compile 'net.dehora.outland:outland-feature-java:0.0.12'\n}  \n```\n\n### SBT\n\nAdd jcenter to `resolvers` in `build.sbt`:\n\n```scala\nresolvers += \"jcenter\" at \"http://jcenter.bintray.com\"\n```\n\nand add the project to `libraryDependencies` in `build.sbt`:\n\n```scala\nlibraryDependencies += \"net.dehora.outland\" % \"outland-feature-client\" % \"0.0.12\"\n```\n\n\n# Build and Development\n\nThe project is built with [Gradle](http://gradle.org/) and uses the \n[Netflix Nebula](https://nebula-plugins.github.io/) plugins. The `./gradlew` \nwrapper script will bootstrap the right Gradle version if it's not already \ninstalled. \n\nThe client and server jar files are build using the wonderful \n[Shadow](https://github.com/johnrengelman/shadow) plugin.\n\n# Contributing\n\nPlease see the [issue tracker](http://bit.ly/2nLZNUT) \nfor things to work on. The [help-wanted](http://bit.ly/2ngXkxP) label  has a list of things \nthat would be nice to have.\n\nBefore making a contribution, please let us know by posting a comment to the \nrelevant issue. If you would like to propose a new feature, create a new issue \nfirst explaining the feature you’d like to contribute or bug you want to fix. Significant \nfeatures will end up being added to the [Trello Roadmap](http://bit.ly/2nje8ou). \n\nThe codebase follows [Square's code style](https://github.com/square/java-code-styles) \nfor Java and Android projects.\n\n----\n\n# License\n\nApache License Version 2.0\n\nCopyright 2017 Bill de hÓra\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n   http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdehora%2Foutland","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdehora%2Foutland","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdehora%2Foutland/lists"}