Code Monkey home page Code Monkey logo

openfeature-sdk-sorbet's Introduction

Sorbet-aware OpenFeature Ruby Implementation

NOTE This implementation has been deprecated in favor of the official Ruby SDK. We recommend switching to that. In the future, we hope to add RBS and Tapioca DSL generation support to the official library.

OpenFeature is an open standard for vendor-agnostic feature flagging. Sorbet is a type-checker for Ruby, built by Stripe. Sorbet provides powerful runtime utilities to achieve things traditionally not possible with Ruby, such as interfaces, immutable structures and enums. This makes it a very good option when defining specifications.

If an organization is not already using Sorbet, you probably don't want to introduce a dependency on sorbet-runtime, which this gem does. As such, this will always be a distinct implementation, separate from the official Ruby SDK.

Current OpenFeature specification target version: 0.5.2 Current supported Ruby versions: 3.1.X, 3.2.X Support for Evaluation Context and Hooks is not complete.

Installation

Install the gem and add to the application's Gemfile by executing:

$ bundle add openfeature-sdk-sorbet

If bundler is not being used to manage dependencies, install the gem by executing:

$ gem install openfeature-sdk-sorbet

Usage

require "open_feature_sorbet"

# Configure global API properties

OpenFeature.set_provider(OpenFeatureSorbet::NoOpProvider.new)
OpenFeature.set_evaluation_context(OpenFeatureSorbet::EvaluationContext.new(fields: { "globally" => "available" }))
OpenFeature.add_hooks([OpenFeatureSorbet::Hook.new]) # experimental, not fully supported

client = OpenFeature.create_client(evaluation_context: OpenFeatureSorbet::EvaluationContext.new(fields: { "client" => "available" }))

# Fetch boolean value
# Also methods available for String, Number, Integer, Float and Structure (Hash)
bool_value = client.fetch_boolean_value(flag_key: "my_toggle", default_value: false) # => (true or false)

# Sorbet sprinkles in type safety
bool_value = client.fetch_boolean_value(flag_key: "my_toggle", default_value: "bad!") # => raises TypeError from Sorbet, invalid default value

# Additional evaluation context can be provided during invocation
number_value = client.fetch_number_value(flag_key: "my_toggle", default_value: 1, context: OpenFeatureSorbet::EvaluationContext.new(fields: { "only_this_call_site" => 10 })) # => merges client and global context

# Fetch structure evaluation details
structure_evaluation_details = client.fetch_structure_details(flag_key: "my_structure", default_value: { "a" => "fallback" }) # => EvaluationDetails(value: Hash, flag_key: "my_structure", ...)

Note on Structure

The OpenFeature specification defines Structure as a potential return type. This is somewhat ambiguous in Ruby, further complicated by T::Struct that we get from Sorbet. For now, the type I've elected here is T.any(T::Array[T.untyped], T::Hash[T.untyped, T.untyped] (loosely, either an Array of untyped members or a Hash with untyped keys and untyped values) for flexibility but with a little more structure than a YML or JSON parsable string. This decision might change in the future upon further interpretation or new versions of the specification.

Evaluation Context

We support global evaluation context (set on the OpenFeature module), client evaluation context (set on client instances or during client initialization) and invocation evaluation context (passed in during flag evaluation). In compliance with the specification, the invocation context merges into the client context which merges into the global context. Fields in invocation context take precedence over fields in the client context which take precedence over fields in the global context.

Provider Abstract Class

By default, this implementation sets the provider to the OpenFeatureSorbet::NoOpProvider which always returns the default value. It's up to the individual teams to define their own providers based on their flag source (in the future, I'll release open-source providers based on various, common vendors).

This gem also provides OpenFeatureSorbet::MultipleSourceProvider to allow fetching flags from multiple sources. This is especially useful if your existing application has flags spread across bespoke and vendor solutions and you want to unify the evaluation sites. It can be instantiated and configured like so:

provider = OpenFeatureSorbet::MultipleSourceProvider.new(
  providers: [
    CustomProvider.new,
    OpenFeatureSorbet::NoOpProvider.new
  ]
)

OpenFeature.set_provider(provider)

Implementing Custom Providers

Thanks to Sorbet abstract classes, it's fairly straightforward to implement a new provider. Here is an example for a JSON-based flag format on disk:

class JsonFileFlagProvider < OpenFeatureSorbet::Provider
  extend T::Sig

  sig { void }
  def initialize
    super(OpenFeatureSorbet::ProviderStatus::NotReady)
  end

  def init(context)
    @file = File.open(context.file || "flags.json")
    @status = OpenFeatureSorbet::ProviderStatus::Ready
  end

  sig { overridable.void }
  def shutdown
    @file.close
  end

  sig { override.returns(OpenFeatureSorbet::ProviderMetadata) }
  def metadata
    OpenFeatureSorbet::ProviderMetadata.new(name: "Json File Flag Provider")
  end

  sig { override.returns(T::Array[Hook]) }
  def hooks
    []
  end

  sig do
    override
      .params(
        flag_key: String,
        default_value: T::Boolean,
        context: T.nilable(EvaluationContext)
      )
      .returns(OpenFeatureSorbet::ResolutionDetails[T::Boolean])
  end
  def resolve_boolean_value(flag_key:, default_value:, context: nil)
    file_input = JSON.parse(File.read("flags.rb"))
    value = file_input.fetch("flag_key", default_value)

    OpenFeatureSorbet::ResolutionDetails.new(
      value: value,
      # ... other optional fields
    )
  end

  # ... other resolver methods
end

By inheriting from the OpenFeatureSorbet::Provider class, Sorbet will indicate what methods it's expecting and what their type signatures should be.

A note on initialize versus init

The Ruby initialize method is the best place to do any direct construction logic for an object, such as setting configuration values. init is called by OpenFeature when setting a provider and is the best place to make any HTTP requests, establish persistent connections, or any other connection logic that could potentially fail. By the end of this method, @status must be set to either Ready or Error.

Development

After checking out the repo, run bin/setup to install dependencies. Then, run rake to run Rubocop and the tests. You can also run bin/console for an interactive prompt that will allow you to experiment.

To install this gem onto your local machine, run bundle exec rake install.

Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/maxveldink/openfeature-sdk-sorbet. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the code of conduct.

License

The gem is available as open source under the terms of the MIT License.

Code of Conduct

Everyone interacting in this project's codebase, issue trackers, chat rooms and mailing lists is expected to follow the code of conduct.

Sponsorships

I love creating in the open. If you find this or any other maxveld.ink content useful, please consider sponsoring me on GitHub.

openfeature-sdk-sorbet's People

Stargazers

 avatar  avatar

Watchers

 avatar  avatar

openfeature-sdk-sorbet's Issues

Drop Ruby 2.7 support

I initially targeted Ruby 2.7 because I had some projects still on that. Since it is EOL, we really should drop the dependency and update our linting to target 3.0. We should probably get to a fully compliant specification version before dropping the support so there is at least one released artifact a team using 2.7 could use.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.