mercredi 25 août 2021

How to avoid fatty jobs in Rails, or even better, How to avoid Active Jobs at all in Rails?

Have you ever seen a code like this using an Old Resque technology?

NOTE: Some details were removed to avoid making any references to a production code like this

# frozen_string_literal: true

class MySuperDuperNotificationsCount < Retryable
  @queue = :integrations

  class << self
    def perform
      # Validating if Notification exist
      return unless msg_type.present?

      # This job will check if doctors have the electronic prescriptions service Notifications and will send
      # a notification per Doctor to each manager
      doctors.each do |doctor|
        send_managers_notification(doctor) unless notifications_empty?(doctor.prescription_notifications)
      end
    end

    private

    def msg_type
      @msg_type ||= MessageType.find_by(group: 'electronic_prescriptions', name: 'doctor notifications count')
    end

    def doctors
      # We need to reduce as much as possible the amount of records to iterate
      # TODO: While this shortens the search there might be other things
      # that we could add like last login time
      @doctors ||= User.with_electronic_prescriptions_enabled
        .enabled_users
        .doctors
        .where.not(prescription_notifications: nil)
    end

    def notifications_count(notifications)
      MyApplicationNamespace::ElectronicPrescriptions::NotificationHelper.notifications_count(notifications)
    end

    def notifications_empty?(prescription_notifications)
      # While the query (doctors) already checks for nil values,
      # due to the way prescription_notifications is build, it can have values
      # but that doesn't mean it has notifications
      # Examples:
      # "[]" No partnerships
      # "[{:clinic_id=>1}, {:clinic_id=>2}]" Two partnerships but no notifications
      # So we need to cover/validate that here

      # This verify if any of the notification types has notifications
      !MyApplicationNamespace::ElectronicPrescriptions::NotificationHelper.has_pending_notifications(
        # At this point prescription_notifications is separated by partnerships
        # So we need to count the total of notifications by type
        notifications_count(prescription_notifications)
      )
    end

    def send_managers_notification(doctor)
      # As required, this will send a Notification to all managers telling them
      # that a specific doctor has pending rcopia notifications
      # 1 Notification per Doctor per manager
      # TODO: Managers for Organization returns staff (including owners)
      # So we need to call managers again (for Users) to scope to Managers only
      doctor.organization.managers.managers.each do |manager|
        # Here we check only for in app notifications settings,
        # if user has the notification enabled we can send the notification,
        # currently this doesn't care aout email settings as the email is not implemented
        notification_enabled = manager.notification_settings.find_by(
          notification_type_id: notification_type.id
        )&.send_in_app

        return unless notification_enabled

        MyApplicationNamespace::Notifications::Manager.queue_notification(
          user_id: manager.id,
          notification_type_id: notification_type.id,
          notifiable_type: doctor.class.to_s,
          notifiable_id: doctor.id,
          payload: notifications_count(doctor.prescription_notifications)
        )
      end
    end
  end
end

And you are running that code every 15 minutes?

What architecture you are following to avoid that?

Well, In this guide we are going to review how to become from that to turn into something great like this:

        ServiceEnqueuerJob.enqueu(
          'GoogleApis::AppointmentCalendarEventCreator',
          'create_event_from',
          appointment_id
        )

or at least like this If you are still using Resque but not Sidekiq

class GoogleCalendarDeleteEventJob
  @queue = :notifications

  class << self
    def enqueue(appointment_id)
      Resque.enqueue(self, appointment_id)
    end

    def perform(appointment_id)
      AppointmentCalendarService.delete_event_for(appointment_id)
    end
  end
end

So you can call it in any place you need it, like in a controller

def create
  if AppointmentEvent.persist_values(params)
    GoogleCalendarDeleteEventJob.enqueue(appt_event.id)
    ...
  else
    ...
  end

Are you interested in the details?

Aucun commentaire:

Enregistrer un commentaire