0.0
No commit activity in last 3 years
No release in over 3 years
Create code boundaries within your Rails monolith
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
 Dependencies

Development

Runtime

 Project Readme

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 is used in surprising ways.

# 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

TODO

  • Hardcode callers that we'll want to whitelist (e.g. Pry and Rails console)
  • Introduce environment-specific options for erroring or warning
  • An ActiveRecord extension (separate gem?) that records where associations and models are defined and doesn't permit crossing internal_api boundaries
  • Exception lists of filenames as option arg to .internal_api

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.