{"id":25776298,"url":"https://github.com/Glassdoor/planout4j","last_synced_at":"2026-08-13T06:30:18.821Z","repository":{"id":31195457,"uuid":"34756186","full_name":"Glassdoor/planout4j","owner":"Glassdoor","description":"Java port of Facebook's PlanOut A/B testing system with additional functionality","archived":false,"fork":false,"pushed_at":"2022-12-14T20:25:12.000Z","size":231,"stargazers_count":120,"open_issues_count":21,"forks_count":48,"subscribers_count":21,"default_branch":"master","last_synced_at":"2026-01-11T19:10:46.480Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Glassdoor.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2015-04-28T21:12:56.000Z","updated_at":"2025-10-10T08:13:36.000Z","dependencies_parsed_at":"2023-01-14T18:32:01.235Z","dependency_job_id":null,"html_url":"https://github.com/Glassdoor/planout4j","commit_stats":null,"previous_names":[],"tags_count":3,"template":false,"template_full_name":null,"purl":"pkg:github/Glassdoor/planout4j","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Glassdoor%2Fplanout4j","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Glassdoor%2Fplanout4j/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Glassdoor%2Fplanout4j/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Glassdoor%2Fplanout4j/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Glassdoor","download_url":"https://codeload.github.com/Glassdoor/planout4j/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Glassdoor%2Fplanout4j/sbom","scorecard":{"id":56824,"data":{"date":"2025-08-11","repo":{"name":"github.com/Glassdoor/planout4j","commit":"566c109062bc1cc18c29fcace0981a810e7802f6"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":2,"checks":[{"name":"Maintained","score":0,"reason":"0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Code-Review","score":0,"reason":"Found 2/22 approved changesets -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Token-Permissions","score":-1,"reason":"No tokens found","details":null,"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Dangerous-Workflow","score":-1,"reason":"no workflows found","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Pinned-Dependencies","score":-1,"reason":"no dependencies found","details":null,"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"License","score":9,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Warn: project license file does not contain an FSF or OSI license."],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":0,"reason":"branch protection not enabled on development/release branches","details":["Warn: branch protection not enabled for branch 'master'"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"SAST","score":0,"reason":"SAST tool is not run on all commits -- score normalized to 0","details":["Warn: 0 commits out of 16 are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}},{"name":"Vulnerabilities","score":2,"reason":"8 existing vulnerabilities detected","details":["Warn: Project is vulnerable to: GHSA-4jrv-ppp4-jm57","Warn: Project is vulnerable to: GHSA-5mg8-w23w-74h3","Warn: Project is vulnerable to: GHSA-7g45-4rm6-3mm3","Warn: Project is vulnerable to: GHSA-mvr2-9pj6-7w5j","Warn: Project is vulnerable to: GHSA-269g-pwp5-87pp","Warn: Project is vulnerable to: GHSA-6hgm-866r-3cjv","Warn: Project is vulnerable to: GHSA-fjq5-5j5f-mvxh","Warn: Project is vulnerable to: GHSA-j288-q9x7-2f5v"],"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}}]},"last_synced_at":"2025-08-15T00:56:16.041Z","repository_id":31195457,"created_at":"2025-08-15T00:56:16.041Z","updated_at":"2025-08-15T00:56:16.041Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":36595822,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-08-06T04:43:03.162Z","status":"online","status_checked_at":"2026-08-13T02:00:06.325Z","response_time":111,"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":[],"created_at":"2025-02-27T06:01:21.135Z","updated_at":"2026-08-13T06:30:18.388Z","avatar_url":"https://github.com/Glassdoor.png","language":"Java","funding_links":[],"categories":["测试"],"sub_categories":[],"readme":"### NOTE \nThis repo is not being maintained any longer. There is active fork at https://github.com/nkconnor/planout4j\n\n[![Build Status](https://travis-ci.org/Glassdoor/planout4j.svg?branch=master)](https://travis-ci.org/Glassdoor/planout4j)\n[![Maven Central](https://maven-badges.herokuapp.com/maven-central/com.glassdoor.planout4j/planout4j-core/badge.svg)](https://maven-badges.herokuapp.com/maven-central/com.glassdoor.planout4j/planout4j-core)\n\n# PlanOut4J\n\nPlanOut4J is a Java-based implementation of Facebook's [PlanOut], an A/B testing framework designed to conduct experiments on the web at scale.\nPlanOut4J makes it easy to express, deploy and maintain sophisticated randomized experiments and to quickly iterate on these experiments, while satisfying the constraints of large-scale Internet services with many users.\n\n[PlanOut]: http://facebook.github.io/planout/\n\nPlanOut4J's emphasis is to enable persons who don't necessarily have comprehensive engineering background create and maintain experiments easily. To that extent, we utilize [PlanOut DSL](http://facebook.github.io/planout/docs/planout-language-reference.html) wrapped into an intuitive YAML format. Here is a simple config file describing a trivial *namespace* (more about namespaces below)\n\n```yaml\nnamespace:\n  unit: userid\n  segments: 100\n\nexperiment_definitions:\n  - definition: Default_Experiment\n    assign: !planout |\n      button_color = '#000000';\n      button_text  = 'Register';\n  - definition: Button_Experiment\n    assign: !planout |\n      button_color = uniformChoice(choices=['#ff0000', '#00ff00'], unit=userid);\n      button_text  = weightedChoice(choices=['Join now!', 'Sign up.'], weights=[0.7, 0.3], unit=userid);\n\ndefault_experiment: Default_Experiment\n\nexperiment_sequence:\n  - action: add\n    definition: Button_Experiment\n    name: Button_Experiment.1\n    segments: 40\n```\n\nWhat's going on here?\n\nFirstly, we need a [namespace](https://facebook.github.io/planout/docs/namespaces.html). One might think of namespaces are containers used to run multiple experiments concurrently without stepping on each others' toes where such interference is undesirable. For a particular user, only a single experiment in a given namespace can be active at any given point in time. However, the same user can participate in multiple experiments from different namespaces. We put experiments that manipulate closely related parameters into the same namespace, thus avoiding something like presenting a user with white text on white background. If we save the above YAML in a file `test.yaml` we have `test` namespace.\n\nWithin namespace config, we define the name of the input parameter which represents out primary unit of traffic segmentation (`userid` in this case) as well as the number of segments to split the traffic into (`100`). Then we define two experiments: `Default_Experiment` and `Button_Experiment`. The former is required to give all parameters initial values which are used when a user is not part of any experiment. The lines following `!planout |` (which represent YAML syntax for assigning a tag and expecting multiline string) are the written in the [PlanOut DSL](http://facebook.github.io/planout/docs/planout-language-reference.html) and can be as trivial or as complicated as needed. The latter experiment involves randomly assigning button color and text, with color being uniformly distributed and text having custom weights. In the last section, `experiment_sequence`, we \"instantiate\" `Button_Experiment` and allocate 40% of traffic to it.\n\n## Project Structure\nThe project is comprised of the following maven modules:\n\n* `core` - the most important module. Defines all PlanOut operations, as well as the central classes `Namespace`, `NamespaceConfig`, `Experiment`, and others. Does not depend on any other modules. If one wants to use PlanOut4J programmatically (i.e. without external configuration) or one already has a parsed JSON object representing namespace based on the above \"schema\", then one needs `core` module only\n* `compiler` - this module provide java wrapper for [PlanOut compiler](https://github.com/facebook/planout/tree/master/compiler) as well as tools and API to compile namespace YAML (see above) with embedded PlanOut DSL into JSON.\n* `config` - this module defines API for reading namespace configuration data from / writing to a *backend*. Currently *file system backend* and *Redis backend* (both used internally at Glassdoor) are provided. The module also exposes `Planout4jRepository` interface which acts as a facade to one or more *backends*. It depends on `compiler` for parsing the data.\n* `api` - this is the primary entry point. It provides `NamespaceFactory` interface and several implementations. It depends on `config` for loading up each individual *namespace* and maintains a cache of those keyed by name. This is what majority of developers will likely use.\n* `tools` - this contains all command-line tools. Tools are described in details in the [usage](USAGE.md) document.\n* `demos` - this is what you think it is\n\n## Maven\nBinary artifacts are hosted at Sonatype (OSSRH) repository and releases are propagated to Maven Central.\nToo use PlanOut4J in a project, add the [latest version of planout4j-api](http://search.maven.org/#search%7Cga%7C1%7Ca%3A%22planout4j-api%22)\nto the dependencies in `pom.xml`, for ex.:\n\n ```xml\n \u003cdependency\u003e\n     \u003cgroupId\u003ecom.glassdoor.planout4j\u003c/groupId\u003e\n     \u003cartifactId\u003eplanout4j-api\u003c/artifactId\u003e\n     \u003cversion\u003e1.1\u003c/version\u003e\n \u003c/dependency\u003e\n ```\n\nThe current set of snapshot artifacts is available [here](https://oss.sonatype.org/content/groups/public/com/glassdoor/planout4j)\n\n## Backends and Loading of Namespaces\nBackend is an abstraction used to access (read/write) namespace configuration data without concern of the underlying storage mechanism.\nCurrently there are two types of backends:\n\n* File system ([implementation](https://github.com/Glassdoor/planout4j/blob/master/config/src/main/java/com/glassdoor/planout4j/config/Planout4jConfigFileBackend.java))\n\t* has separate properties for read (source) and write (target) use\n* Redis ([implementation](https://github.com/Glassdoor/planout4j/blob/master/config/src/main/java/com/glassdoor/planout4j/config/Planout4jConfigRedisBackend.java))\n\nBackends come into play in two cases:\n\n1. `NamespaceFactory` implementation uses [Planout4jConfigRepositoryImpl](https://github.com/Glassdoor/planout4j/blob/master/config/src/main/java/com/glassdoor/planout4j/config/Planout4jRepositoryImpl.java) to fetch the namespace configs (either in their source YAML form or previously compiled to JSON). Examples:\n  * Redis backend serving JSON\n  * File backend surving original YAML files (compiling on-demamd)\n2. `ShipperTool` uses [Planout4jShipperImpl](https://github.com/Glassdoor/planout4j/blob/master/config/src/main/java/com/glassdoor/planout4j/config/Planout4jShipperImpl.java) to get all namespaces from **source** backend, compile \u0026 validate them, and store in **target** backend. File system - to - File system and File system - to - Redis are reasonable examples of the shipper setup.\n\nPlease see the [default configuration file](https://github.com/Glassdoor/planout4j/blob/master/config/src/main/resources/planout4j.conf) to learn about the settings and ways to override them.\n\n## Using PlanOut4J\nPlease see detailed instructions [here](USAGE.md)\n\nAlso please take a look at [demos](https://github.com/Glassdoor/planout4j/tree/master/demos/src/main/java/com/glassdoor/planout4j/demos)\n\nIf you have any questions and/or suggestions, please join [PlanOut4J Google group](https://groups.google.com/d/forum/planout4j)\n\n## PlanOut4J at Glassdoor\n\nAt Glassdoor, we have been using PlanOut4J extensively for several months conducting real-life production experiments at large scale (1 mil parameter lookups per day for each of around 10 namespaces). We are very grateful to Facebook for open-sourcing the original PlanOut and we hope that our java port with additional features will help to extend these robust A/B testing ideas to many teams who use Java as the primary programming language.\n\n### More on Configuration\n\nWe actually use a pretty interesting configuration pipeline at Glassdoor. It works as following:\n\n* A dedicated Git repository is maintained for all namespace config (yaml) files\n* The repository is curated by data scientists and product managers\n* When a change is made on \"production\" branch (presumably via a pull request), a Jenkins job picks it up, and performs the following:\n  * compile affected namespace(s) from YAML to JSON (with validation)\n  * store the result JSON in Redis with namespace name as a part of the key\n* Our instance of `RefreshingNamespaceFactory` is configured to use Redis backend and every 2 mins pulls all namespace data from Redis.\n\nThis scheme allowed us to achieve high level of automation, good system of checks and balances (git), and fast performance in a distributed environment (Redis, getting data in JSON).\n\n### Overriding Parameters\n\nOur web front-end infrastructure contains code that looks for custom planout4j headers. We use this mechanism to be able to override any parameters set in any of the experiments. This seemed to us a cleaner approach as compared to using query string (which is often transformed by existing logic). There are handy browser extensions for manipulating headers.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FGlassdoor%2Fplanout4j","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FGlassdoor%2Fplanout4j","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FGlassdoor%2Fplanout4j/lists"}