{"id":13543926,"url":"https://github.com/Nike-Inc/moirai","last_synced_at":"2025-04-02T13:31:19.131Z","repository":{"id":54282901,"uuid":"102787572","full_name":"Nike-Inc/moirai","owner":"Nike-Inc","description":"Libraries that can be used to determine if a feature should be exposed to a user.","archived":false,"fork":false,"pushed_at":"2021-02-26T19:06:23.000Z","size":90,"stargazers_count":53,"open_issues_count":1,"forks_count":13,"subscribers_count":9,"default_branch":"main","last_synced_at":"2025-03-29T12:33:48.768Z","etag":null,"topics":[],"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/Nike-Inc.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2017-09-07T21:30:57.000Z","updated_at":"2024-09-12T08:24:05.000Z","dependencies_parsed_at":"2022-08-13T10:51:01.668Z","dependency_job_id":null,"html_url":"https://github.com/Nike-Inc/moirai","commit_stats":null,"previous_names":[],"tags_count":7,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Nike-Inc%2Fmoirai","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Nike-Inc%2Fmoirai/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Nike-Inc%2Fmoirai/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Nike-Inc%2Fmoirai/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Nike-Inc","download_url":"https://codeload.github.com/Nike-Inc/moirai/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":246823696,"owners_count":20839764,"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","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":"2024-08-01T11:00:38.893Z","updated_at":"2025-04-02T13:31:14.121Z","avatar_url":"https://github.com/Nike-Inc.png","language":"Java","funding_links":[],"categories":["Java","功能切换"],"sub_categories":[],"readme":"# Moirai\n\n[![Download](https://api.bintray.com/packages/nike/maven/moirai-core/images/download.svg)](https://bintray.com/nike/maven/moirai-core/_latestVersion)\n![Build](https://github.com/Nike-Inc/moirai/workflows/Build/badge.svg)\n[![Code Coverage](https://img.shields.io/codecov/c/github/Nike-Inc/moirai/master.svg)](https://codecov.io/github/Nike-Inc/moirai?branch=master)\n [![License][license img]][license]\n\n[Moirai](https://en.wikipedia.org/wiki/Moirai) (or: the Fates) controls people's destiny. Moirai is a feature-flag and resource-reloading library for the JVM (requires Java 8 or above).\n\nThis project provides libraries that can be used to determine if a feature should be exposed to a user. This feature-flagging can be used for alpha or beta testing, load control, gradual rollout, A/B testing, etc.\n\nIt consists of support for reloading a configuration resource periodically in the background, and pluggable interfaces for how resources are loaded, what their content is, and how the configuration is used to decide if a feature is enabled for a user. Modules are then provided for reusable implementations of these components.\n\nThe resource reloading can be used independently of the feature-flagging as a light-weight configuration reloading library.\n\n## Usage\n\nThe provided example is in Java, but Moirai should be easy to use in any JVM language.\n\nCreates a `FeatureFlagChecker` that reloads a Typesafe config file that controls whether `getNumber` returns a hard-coded number or calls `calculateRealNumber`:\n\n```java\nimport com.nike.moirai.ConfigFeatureFlagChecker;\nimport com.nike.moirai.FeatureCheckInput;\nimport com.nike.moirai.FeatureFlagChecker;\nimport com.nike.moirai.Suppliers;\nimport com.nike.moirai.resource.FileResourceLoaders;\nimport com.nike.moirai.resource.reload.ResourceReloader;\nimport com.nike.moirai.typesafeconfig.TypesafeConfigDecider;\nimport com.nike.moirai.typesafeconfig.TypesafeConfigReader;\nimport com.typesafe.config.Config;\n\nimport java.io.File;\nimport java.util.function.Supplier;\n\nimport static com.nike.moirai.Suppliers.supplierAndThen;\n\npublic class Usage {\n    Supplier\u003cString\u003e fileSupplier = FileResourceLoaders.forFile(new File(\"/path/to/conf/file/moirai.conf\"));\n    Supplier\u003cConfig\u003e configSupplier = supplierAndThen(fileSupplier, TypesafeConfigReader.FROM_STRING);\n\n    ResourceReloader\u003cConfig\u003e resourceReloader = ResourceReloader.withDefaultSettings(\n        Suppliers.async(configSupplier),\n        configSupplier.get()\n    );\n\n    FeatureFlagChecker featureFlagChecker = ConfigFeatureFlagChecker.forReloadableResource(\n        resourceReloader,\n        TypesafeConfigDecider.ENABLED_USERS.or(TypesafeConfigDecider.PROPORTION_OF_USERS)\n    );\n\n    public int getNumber(String userIdentity) {\n        if (featureFlagChecker.isFeatureEnabled(\"random.calculatenumber\", FeatureCheckInput.forUser(userIdentity))) {\n            return calculateRealNumber();\n        } else {\n            return 42;\n        }\n    }\n\n    public int calculateRealNumber() {\n        // calculate a value\n        ...\n    }\n}\n```\n\nExample config file:\n\n```\nmoirai {\n  random.calculatenumber {\n    enabledUserIds = [\n      8675309\n      1234\n    ]\n   enabledProportion = 0.01\n  }\n}\n```\n\n## Modules\n\n* `moirai-core` provides base functionality and abstractions with no additional dependencies beyond the JDK\n* `moirai-s3` provides a convenient `Supplier` for loading a text file from Amazon S3\n* `moirai-typesafeconfig` provides both a utility to read a String as a Typesafe `Config` and `Predicate` implementations based on a convention for representing feature-flag settings as a `Config`.\n* `moirai-riposte-example` provides an example of how one would go about using riposte to use the moirai's `ConfigFeatureFlagChecker`\n\n## Components\n\n### ResourceReloader\n\nThe `ResourceReloader` takes an arbitrary `Supplier` of some value, and periodically calls the `Supplier` and stores the resulting value. It's essentially a cache of one value that is periodically updated asynchronously. Any data type and data source can be used by providing the `Supplier`.\n\nYou can have as much or as little transformation of the raw resource data cached as you want by chaining behavior onto provided `Supplier`. \n\n### Suppliers\n\nThere are utilities in `Suppliers` for transforming both synchronous and asynchronous `Supplier` instances. Additional modules can provide a `Supplier` for loading data from some location (such an object in S3). `FileResourceLoaders` provides instances for reading from the file system or the classpath. Modules can also provide functions to be used with the `Suppliers` for reading raw data into a useful format (such as Typesafe `Config`). \n\n### FeatureFlagChecker\n\n`FeatureFlagChecker` is a very simple interface that takes in a feature-identifier and a `FeatureCheckInput` and returns a boolean if the feature should be enabled given that input.\n\nThe `FeatureCheckInput` represents any data that might be used to make a feature-check decision. It has built-in support for a userId and a dateTime, and also allows custom dimensions to be added to fit whatever input decision criteria you need.\n\n### Config\n\nTwo methods of support for making a feature flag decision based on the userId input are provided: `WhitelistedUsersConfigDecider` and `ProportionOfUsersConfigDecider`. These are generic abstractions that modules (such as `moirai-typesafeconfig`) can provide concrete implementations of for specific config formats. The abstraction for these \"deciders\" is a `Predicate`, which allows you to combine them using `and` or `or` to flexibly define your own rules for feature checking.\n\n* `WhitelistedUsersConfigDecider` takes a concrete list of user identifiers from some config source and checks if it contains the input user\n* `ProportionOfUsersConfigDecider` uses the `hashCode` of the input userId against a ratio provided by some config source. For example, if the config source provides a value of 0.9 for the feature identifier, then approximately 90% of the users will get the feature enabled from this decider. \n\nAnother method for making a feature flag decision based on a boolean value in the configuration: `FeatureEnabledConfigDecider`. This decider does not take any input.\n\n### ConfigFeatureFlagChecker\n\n`ConfigFeatureFlagChecker` is where you put it all together. Given either a `ResourceReloader` or just a `Supplier` and a corresponding `Predicate`, it provides a `FeatureFlagChecker`.\n\nWhile the example from the Usage section demonstrates the intended common pattern for combining these components together, Moirai is designed to be flexible and composable. So you can build your `ResourceReloader`/`Supplier` and your `Predicate` however you want, including with custom implementations, or you can even implement `FeatureFlagChecker` directly if desired.\n\n## License\n\nMoirai is released under the [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0)\n\n[license]:LICENSE.txt\n[license img]:https://img.shields.io/badge/License-Apache%202-blue.svg\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FNike-Inc%2Fmoirai","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FNike-Inc%2Fmoirai","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FNike-Inc%2Fmoirai/lists"}