$30 off During Our Annual Pro Sale. View Details »

Keeping it Ruby: Why Your Product Needs a Ruby ...

Keeping it Ruby: Why Your Product Needs a Ruby SDK - RubyWorld 2024

What is the difference between a good project and a great one for developers on Ruby? We think that one part of it is having an amazing Ruby SDK.

The first question everyone is going to ask when they want to use your product is - where's the gem? We are going to look into what makes a Ruby SDK to be a great one, and how your team can start implementing one.

Having a great client library not only can increase adoption of your product, but can simplify application, speed up development, save costs for your client, and even make your users shout out to you out of happiness.

In this talk we would like to explain how a great Ruby client with opinionated API matching Ruby/Rails semantics can make a difference for your users. Using imgproxy.rb and imgproxy-rails, client Ruby libraries for imgproxy, an open source image processing server, as an example of a customer acquisition success story via SDK. We'll demonstrate how to replace background jobs with on-the-fly image processing, improving performance while reducing infrastructure costs - and how its Ruby SDK plays into this.

Andrey Novikov

December 05, 2024
Tweet

More Decks by Andrey Novikov

Other Decks in Programming

Transcript

  1. Keeping it Ruby: Why Your Product Needs a Ruby SDK

    Sampo Kuokkanen, Andrey Novikov Evil Martians RubyWorld Conference 2024 05 December 2024
  2. Sampo Kuokkanen Sampo Kuokkanen Sampo Kuokkanen Sampo Kuokkanen Sampo Kuokkanen

    Head of Evil Martians Japan Head of Evil Martians Japan Head of Evil Martians Japan Head of Evil Martians Japan Head of Evil Martians Japan Ruby enthusiast Ruby enthusiast Ruby enthusiast Ruby enthusiast Ruby enthusiast A fan of imgproxy A fan of imgproxy A fan of imgproxy A fan of imgproxy A fan of imgproxy Andrey Novikov Andrey Novikov Andrey Novikov Andrey Novikov Andrey Novikov Ruby developer at Evil Martians Ruby developer at Evil Martians Ruby developer at Evil Martians Ruby developer at Evil Martians Ruby developer at Evil Martians Open source enthusiast Open source enthusiast Open source enthusiast Open source enthusiast Open source enthusiast imgproxy early adopter imgproxy early adopter imgproxy early adopter imgproxy early adopter imgproxy early adopter
  3. Martian Open Source Ruby Next makes modern Ruby code run

    in older versions and alternative implementations Yabeda: Ruby application instrumentation framework Lefthook: git hooks manager AnyCable: Polyglot replacement for ActionCable server PostCSS: A tool for transforming CSS with JavaScript Imgproxy: Fast and secure standalone server for resizing and converting remote images Overmind: Process manager for Procfile-based applications and tmux Even more at evilmartians.com/oss Today's topic
  4. Ruby’s Continuing Popularity RubyGems Downloads Over 100 billion total downloads

    Growing year over year Active ecosystem GitHub Statistics Top 10 most popular language! Strong in web development Active community Ruby Ecosystem RubyGems Rails 100B+ Downloads We Love Gems! Startup Favorite Active & Friendly Community Regular Updates Investments!
  5. The common problem for any web app We need to

    store them and show in various places, of course! And for this we need to: Generate thumbnails to save bandwidth Crop to fit design Add watermarks to prevent theft … Handling images uploaded by users: profile pictures, product photos, reviews, …
  6. “Classic” way Upload image to the server Probably among other

    form fields Store it somewhere Often on S3 or other cloud storage Generate all required thumbnails As many as your design requires Store them somewhere Again S3 or other cloud storage Serve them to the user CDN will help here ImageStorage JobQueue Storage Server User ImageStorage JobQueue Storage Server User Job started Generates thumbnail Uploads Image Stores Image Queues Job Upload successful Requests thumbnail Retrieves Thumbnail There are no thumbnails yet! “Image is processing” ​ Retrieve image Requests thumbnail Retrieves Thumbnail Oh yes, of course, here it is Returns thumbnail Unpredictable latency here
  7. Problems of “classic” approach Hard to predict latency: background jobs

    can queue It can take a while to get your image processed, and “image is processing” fallbacks are ugly Hard to add new variants: need to reprocess all images Possibly millions of jobs to run before enabling it on the front-end And hard to clean up old ones Space is cheap, but not free Deployment: gets complicated You need to install ImageMagick or libvips on all servers/containers Security: it is your headache Processing images on your servers is a security and stability risk, e.g. PNG decompression bomb.
  8. Do we have to do things this way? What if

    we could just generate thumbnails on the fly?
  9. Meet image processing servers They do just one thing, but

    do it well There are many of them: imaginary thumbor cloudinary imgix imagor imgproxy (our favorite ✨) imgproxy Storage Server User imgproxy Storage Server User generate thumbnail on the fly Uploads Image Notifies about upload Thumbnail URL Requests thumbnail Retrieves image Respond with thumbnail
  10. Solving it with on-the-fly processing Complexity: replace your code with

    a microservice Throw away all these background jobs, and replace them with a simple URL construction. Latency: dedicated service that do only images processing Very performant per se, and you can scale it independently from your main application, also add CDN in front of it Adding new variants: just construct new URL Construct new URL, request it, done! Cleaning up old ones: let CDN caches to expire Do you really need to store thumbnails at all? Care only for originals. Security and stability: it is separate from your main application It handles image bombs, and other nasty stuff, but even if some malicious code will be executed, it will find itself in empty Docker container without anything in it.
  11. Which one to choose? Should it be one written in

    Ruby? But if it is a dedicated service, does it matter? Maybe it is better to choose most performant one? Should it be one that is easy to use from Ruby? What are you looking first for when choosing a new dependency?
  12. Introducing imgproxy Open source image processing server Written in Go

    and C for performance Uses libvips for optimal image processing Dockerized and easy to deploy Most Ruby-friendly solution Started at Evil Martians Used by companies big and small: Bluesky, dev.to, Photobucket, eBay, … 1. There is a gem! Two of them! ↩︎ [1]
  13. Technical example: URL signing The only thing a client need

    to care about is constructing URLs to images processed through imgproxy. Given original image URL: Result URL to get 300×150 thumbnail for Retina displays, smart cropped, and saturated, with watermark in right bottom corner: See https://docs.imgproxy.net/generating_the_url https://mars.nasa.gov/system/downloadable_items/40368_PIA22228.jpg https://demo.imgproxy.net/ doqHNTjtFpozyphRzlQTHyBloSoYS13lLuMDozTnxqA/ rs:fill:300:150:1/dpr:2/g:ce/sa:1.4/ wm:0.5:soea:0:0:0.2/wmu:aHR0cHM6Ly9pbWdwcm94eS5uZXQvd2F0ZXJtYXJrLnN2Zw/ plain/ https:%2F%2Fmars.nasa.gov%2Fsystem%2Fdownloadable_items%2F40368_PIA22228.jpg Digital signature Processing options Original image URL
  14. Plain Ruby implementation It is easy to implement yourself (for

    one specific use case) require 'base64' require 'openssl' key = ['943b421c9eb07c83...'].pack('H*') salt = ['520f986b998545b4...'].pack('H*') def generate_url(url, width, height) encoded_url = Base64.urlsafe_encode64(url).tr('=', '') encoded_url = encoded_url.scan(/.{1,16}/).join('/') path = "/resize:fill:#{width}:#{height}/#{encoded_url}" hmac = OpenSSL.hmac( OpenSSL::Digest.new('sha256'), key, "#{salt}#{path}" ) signature = Base64.urlsafe_encode64(hmac).tr('=', '') "http://imgproxy.example.com/#{signature}#{path}" end url = generate_url("http://example.com/image.jpg", 300, 400)
  15. With imgproxy gem But always better to use a battle-tested

    library that will hide all gory details require 'imgproxy' Imgproxy.configure do |config| # Full URL to where your imgproxy lives. config.endpoint = "http://imgproxy.example.com" # Hex-encoded signature key and salt config.key = '943b421c9eb07c83...' config.salt = '520f986b998545b4...' end <%# show.erb.html %> <%= image_tag Imgproxy.url_for( "http://images.example.com/images/image.jpg", width: 500, height: 400, resizing_type: :fill ) %> imgproxy.rb gem
  16. ActiveStorage + imgproxy What is even better: to use familiar

    API and don’t change your codebase! You don’t even have to know that you are using imgproxy! ✨ And you can migrate the whole application to imgproxy in an hour! # Gemfile gem 'imgproxy-rails' # development.rb: use built-in Rails proxy config.active_storage.resolve_model_to_route = :rails_storage_proxy # production.rb: use imgproxy config.active_storage.resolve_model_to_route = :imgproxy_active_storage <%# show.erb.html %> <%= image_tag Current.user.avatar.variant(resize: "100x100") %> imgproxy-rails gem
  17. Let the community speak I clicked the button, deployed the

    OSS version and hooked up the imgproxy.rb ruby gem in my app in under an hour. Within a few weeks, we had switched over all of our upload, template, and graphic previews to Imgproxy… Doing so resulted in the removal of hundreds of lines of code while also enabling new functionality. — John Nunemaker: Ruby programmer and founder, author of flipper and httparty gems https://www.johnnunemaker.com/imgproxy/ Imgproxy is Amazing
  18. Why to “keep it Ruby?” Answer is in this quote

    from the previous slide: I clicked the button, deployed the OSS version and hooked up the imgproxy.rb ruby gem in my app in under an hour. It wouldn’t be possible without a ready to use Ruby gem! Why to spend time and effort to provide official Ruby SDK?
  19. Keep it Ruby! Thank you! @imgproxy @imgproxy_net @imgproxy.net @imgproxy imgproxy.net

    @evilmartians @evilmartians @[email protected] @evilmartians.com evilmartians.com Our awesome blog: evilmartians.com/chronicles! See these slides at envek.github.io/rubyworld-keep-it-ruby