This is a tiny PoC that shows a Rails API-Only app, communicated with via CLI -> ActionCable (WebSockets). It also has RedLock for distributed locking. This was based on [https://www.hansschnedlitz.com/2021/04/04/build-real-time-clis-with-actioncable.html] with some minor changes.
This is for demonstration purposes only!! If you want to actually extend this thing and put it to use, fork it and start a real project! Keep this repo as bare-bones as possible.
Here is the command-line used to generate the initial Rails app:
rails new actioncable-cli \
--api \
--skip-action-mailer \
--skip-action-mailbox \
--skip-action-text \
--skip-active-job \
--skip-active-record \
--skip-active-storage \
--skip-javascript \
--skip-jbuilder \
--skip-spring \
--skip-test \
--skip-system-test \
--skip-webpack-install \
--skip-turbolinks
You're probably going to want AR and other things if you were doing a real app.
Rails is a powerful framework with a huge community, but sometimes you don't really want a WebApp. You just want the ORM, messaging, logging, etc. for your CLI app. You'll get all of that, plus the benefits of WebSockets. WS brings a lot of message bus features to the table without needing to run an actual message bus like RabbitMQ or ActiveMQ.
AND, this architecture will allow your CLI to scale prittay, prittay, prittay, prittayyyy well.
Redis Server either running on localhost:6379 OR you can export REDIS_URL in the environment to use whatever. Don't clobber something important in an important Redis box!
Probably want RVM working, I put this together on Ruby 3.2.0.
bundle install
Use the bin/run-backend script to start Rails and Sidekiq in a terminal window. This script will exit if you hit any key or ctrl-c it. This is the easy way.
Alternatively to run the backend manually:
Start Rails and Sidekiq (you will need two terminals unless you put them in the background):
bundle exec rails server
bundle exec sidekiq
The CLI in this case is just a simple Thor task for demonstration purposes. In real life you'd probably have a small script in bin to load Thor and use it more like a traditional CLI. There is nothing stopping you from using another CLI framework though.
bundle exec thor worker:start
You will see a worker executes on the backend and streams its progress back to your client (the thor task).
The thor worker:start task (located in lib/tasks/worker.thor), connects to Rails' websocket server running on localhost:3000, and it does so asynchronously. It connects to the route /cable, which is the the default ActionCable route, and it passes a "client_id" parameter to the /cable route.
These parameters are processed in app/channels/application_cable/connection.rb
In there, the 'connect' method is called when a connection is made, and that's how you set WebSocket session parameters.
Rails sends you a message of type 'welcome,' which calls a callback in the thor worker code called "on_connected."
The on_connected callback sends a subscribe message to ActionCable for the channel 'WorkerChannel' which is defined in app/channels/worker_channel.rb.
The WorkerChannel's subscribe method calls stream_for to client_#{client_id}.
This is the reference that the rest of the app will use to send messages to this client.
Once the Thor task receives this confirmation (remember it is waiting in an async loop executing callbacks on each message received), it calls the 'on_subscribed' method on account of the message type (confirm_subscription).
The on_subscribed callback hits the /workers/start route (defined with all routes in config/routes.rb) which is configured to route to the WorkersController's start method. The task passes its client_id in the request parameters, which allows us to communicate back to the client since the WorkerChannel utilized its client ID to create a streaming channel.
Worker is a class defined in app/workers/worker.rb. This is a Sidekiq worker that really doesn't do much but sleep and broadcast messages to the client.
The Thor task will receive various messages that the worker started, updated, etc which you can see in the Worker class (app/workers/worker.rb). Note the callbacks on the Thor task to see what draws the progress bar on the screen.
This is very close to a default Rails app with a small amount of code added to show you how easy it is to get going with such advanced architecture. To see these changes run:
git diff 93556557bcee94711920d3692c5abe0ad57a897b..9be03fb7d0fe4c9a4a9c75750fb92fc2b7c830aa