Ruby internal_api

This is a gem for decomposing monolithic Rails apps. It allows you to specify an internal class or module that serves as the internal api for another class or module.

Example:

# Any class or module can be the interface for a portion of your app.
# Imagine trying to collect all payment-related code in your app such that you
# can guarantee none of it used except in the way you expect.
#
# You'll want to somehow encapsulate your models
class PaymentRecord < ActiveRecord::Base
  internal_api PaymentApi
end

# And any helper modules or classes
module Payments
  class Issue
    internal_api PaymentApi
  end
end

# And all you need is some object that you specify as the internal_api for all your code.
module PaymentApi
  def charge(amount_cents, options = {})
    # This is whatever bespoke, ugly code you've inherited in your existing app.
    record = PaymentRecord.create(amount_cents)
    issue = Payments::Issue.new(record).complete!
    log(issue)
  end
end

# So when someone adds a new dependency to your internal code they fail their unit tests:
module Onboarding
  def self.complete!(user)
    PaymentRecord.create(1_00, type: auth) 
    # other onboarding stuff
  end
end
Onboarding.complete(@user) #! Only `PaymentApi` methods can execute PaymentApi code.

The internal_api call rewrites the public methods on your internal code to ensure that it can only be called if PaymentApi is somewhere in the call stack. This allows you to hide an entire portion of your application behind an interface of some kind and have confidence it's reasonable well encapsulated.

Installation

Add this line to your application's Gemfile:

gem 'internal_api'

Performance

This API enforcement is purely dyamic - internal_api modified the runtime execution of code and checks for correct access patterns. This has a nonzero but completely negligible impact on performance and therefore can safely be run in production.

If you come from a Java background you may be used to thinking of backtraces as very expensive. In Ruby they're quite cheap - the backtrace is always available in memory and accessing it only requires turning the C stack into (simple) Ruby objects.

Constructing a single backtrace in running production code takes only a few microseconds on any modern CPU:

>> Benchmark.measure { 1_000_000.times { Kernel.caller_locations }}.real
=> 5.190758000011556

Contributing

Patches welcome, forks celebrated. This project is a safe, welcoming space for collaboration, and contributors will adhere to the Contributor Covenant code of conduct.

License

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

Code of Conduct

Everyone interacting in the InternalApi project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the code of conduct.