mercredi 6 juin 2018

How to reduce modules coupling

Consider Sinatra application on Sinatra (or Rails is not important). I have 2 separated components: Payments and UserProfile. Each component is in its catalog:

payments
  routes.rb
  payment_entity.rb
  payments_confirmation.rb

user_profile
  user_profile_assigner.rb

Payments has route /api/payments/notification. The external payment system calls webhook to this route when the user make a payment. routes.rb - is simple entry point which receive requests from external world and call application service, in this case call PaymentsConfirmation.

# routes.rb
post '/api/payments/notification' do
    PaymentsConfirmation.process(params[:user_id])
end

PaymentsConfirmation changes status of payment. Responsility of Payments module is processing payments. This module should not know about user privilegies.

# payments_confirmation.rb
class PaymentsConfirmation
    def self.process(user_id)
        # confirm payment
        payment_model = PaymentModel.find_by(user_id: user_id)
        payment_model.update_attributes(status: :PAID)

        # assign premium to user
        UserProfileAssigner.make_premium(user_id)
    end
end

UserProfileAssigner assign premium flag to user

# user_profile_assigner.rb
class UserProfileAssigner
    def self.make_premium(user)
        assign_model = AssignModel.find_by(user_id: user.id)
        assign_model.update_attributes(premium: true)
    end
end

I'm concerned about the coupling of modules

I thought about several solution, but none of them seems to me quite optimal:

  1. Call UserProfileAssigner from routes.rb. I think that this will add to the routes extra responsibility.
  2. pass UserProfileAssigner to PaymentsConfirmation with dependency injection. Again, the PaymentsConfirmation should know only about self responsibility, but not that there is some module that has to do something else.
  3. PaymentsConfirmation should send some event to some EventBus (eg SUCCESS). UserProfileAssigner should subscribe to SUCCESS event, and process when SUCCESS will fired. This method seems to me unreasonably complex.
  4. I should create add-on component, which will called from route (eg ExternalPaymentNotificationProcessor). ExternalPaymentNotificationProcessor calls PaymentsConfirmation and UserProfileAssigner sequentially.

What is the best practices to reduce the modules coupling for similar cases?

Aucun commentaire:

Enregistrer un commentaire