# PubSubModelSync Permit to sync models data and make calls between rails apps using google or rabbitmq or apache kafka pub/sub service. Note: This gem is based on [MultipleMan](https://github.com/influitive/multiple_man) which for now looks unmaintained. ## Features - Sync CRUD operations between Rails apps. So, all changes made on App1, will be reflected on App2, App3. Example: If User is created on App1, this user will be created on App2 too with the accepted attributes. - Ability to make class level communication Example: If User from App1 wants to generate_email, this can be listened on App2, App3, ... to make corresponding actions - Change pub/sub service at any time ## Installation Add this line to your application's Gemfile: ```ruby gem 'pub_sub_model_sync' gem 'google-cloud-pubsub', '>= 1.9' # to use google pub/sub service gem 'bunny' # to use rabbit-mq pub/sub service gem 'ruby-kafka' # to use apache kafka pub/sub service ``` And then execute: $ bundle install ## Usage - Configuration for google pub/sub (You need google pub/sub service account) ```ruby # initializers/pub_sub_config.rb PubSubModelSync::Config.service_name = :google PubSubModelSync::Config.project = 'google-project-id' PubSubModelSync::Config.credentials = 'path-to-the-config' PubSubModelSync::Config.topic_name = 'sample-topic' ``` See details here: https://github.com/googleapis/google-cloud-ruby/tree/master/google-cloud-pubsub - configuration for RabbitMq (You need rabbitmq installed) ```ruby PubSubModelSync::Config.service_name = :rabbitmq PubSubModelSync::Config.bunny_connection = 'amqp://guest:guest@localhost' PubSubModelSync::Config.queue_name = 'model-sync' PubSubModelSync::Config.topic_name = 'sample-topic' ``` See details here: https://github.com/ruby-amqp/bunny - configuration for Apache Kafka (You need kafka installed) ```ruby PubSubModelSync::Config.service_name = :kafka PubSubModelSync::Config.kafka_connection = [["kafka1:9092", "localhost:2121"], { logger: Rails.logger }] PubSubModelSync::Config.topic_name = 'sample-topic' ``` See details here: https://github.com/zendesk/ruby-kafka - Add publishers/subscribers to your models (See examples below) - Start subscribers to listen for publishers (Only in the app that has subscribers) ```ruby rake pub_sub_model_sync:start ``` Note: Publishers do not need todo this Note2 (Rails 6+): Due to Zeitwerk, you need to load listeners manually when syncing without mentioned task (like rails console) ```ruby # PubSubModelSync::Config.subscribers ==> [] PubSubModelSync::Runner.preload_listeners # PubSubModelSync::Config.subscribers ==> [# show advanced log messages - ```.logger = Rails.logger``` (Logger) => define custom logger - ```.disabled_callback_publisher = ->(_model, _action) { false }``` (true/false*) => if true, does not listen model callbacks for auto sync (Create/Update/Destroy) - ```.on_before_processing = ->(payload, {subscriber:}) { puts payload }``` (Proc) => called before processing received message (:cancel can be returned to skip processing) - ```.on_success_processing = ->(payload, {subscriber:}) { puts payload }``` (Proc) => called when a message was successfully processed - ```.on_error_processing = ->(exception, {payload:, subscriber:}) { payload.delay(...).process! }``` (Proc) => called when a message failed when processing (delayed_job or similar can be used for retrying) - ```.on_before_publish = ->(payload) { puts payload }``` (Proc) => called before publishing a message (:cancel can be returned to skip publishing) - ```.on_after_publish = ->(payload) { puts payload }``` (Proc) => called after publishing a message - ```.on_error_publish = ->(exception, {payload:}) { payload.delay(...).publish! }``` (Proc) => called when failed publishing a message (delayed_job or similar can be used for retrying) ## TODO - Add alias attributes when subscribing (similar to publisher) - Add flag ```model.ps_process_payload``` to retrieve the payload used to process the pub/sub sync - Auto publish update only if payload has changed - On delete, payload must only be composed by ids - Feature to publish multiple message at a time with the ability to exclude similar messages by klass and action (use the last one) PubSubModelSync::MessagePublisher.batch_publish({ same_keys: :use_last_as_first|:use_last|:use_first_as_last|:keep*, same_data: :use_last_as_first*|:use_last|:use_first_as_last|:keep }) - Add DB table to use as a shield to skip publishing similar notifications or publish partial notifications (similar idea when processing notif) - add callback: on_message_received(payload) ## Q&A - Error "could not obtain a connection from the pool within 5.000 seconds" This problem occurs because pub/sub dependencies (kafka, google-pubsub, rabbitmq) use many threads to perform notifications where the qty of threads is greater than qty of DB pools ([Google pubsub info](https://github.com/googleapis/google-cloud-ruby/blob/master/google-cloud-pubsub/lib/google/cloud/pubsub/subscription.rb#L888)) To fix the problem, edit config/database.yml and increase the quantity of ```pool: 10``` - Google pubsub: How to process notifications parallely and not sequentially (default 1 thread)? ```ruby PubSubModelSync::ServiceGoogle::LISTEN_SETTINGS = { threads: { callback: qty_threads } } ``` Note: by this way some notifications can be processed before others thus missing relationship errors can appear - How to retry failed syncs with sidekiq? ```ruby # lib/initializers/pub_sub_config.rb class PubSubRecovery include Sidekiq::Worker sidekiq_options queue: :pubsub, retry: 2, backtrace: true def perform(payload_data, action) payload = PubSubModelSync::Payload.from_payload_data(payload_data) payload.send(action) end end PubSubModelSync::Config.on_error_publish = lambda do |_e, data| PubSubRecovery.perform_async(data[:payload].to_h, :publish!) end PubSubModelSync::Config.on_error_processing = lambda do |_e, data| PubSubRecovery.perform_async(data[:payload].to_h, :process!) end ``` ## Contributing Bug reports and pull requests are welcome on GitHub at https://github.com/owen2345/pub_sub_model_sync. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct. ## License The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT). ## Code of Conduct Everyone interacting in the PubSubModelSync project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/[USERNAME]/pub_sub_model_sync/blob/master/CODE_OF_CONDUCT.md).