Rails:两应用共享同一数据库,如何让App1感知App2新增记录?
问题:拆分Rails应用后,Web应用如何感知API应用的数据库插入操作?
我原本有个同时管理Web应用和API的Rails项目,现在为了优化服务器资源,打算把两者拆成独立应用:
- App1:端口80的Web服务,共用数据库DB1
- App2:端口8080的API服务,同样使用DB1
核心需求:当App2插入新的Post记录时,App1需要触发对应的业务逻辑。目前考虑了几个方向:Rpush、SQL触发器,或者把API转成gem,想了解各方案的优劣及实现方式。
现有代码片段
App1的Post模型:
class Post < ActiveRecord::Base belongs_to :user before_save :do_stuff_when_app2_insert_record # 跨应用场景下该钩子不生效 def do_stuff_when_app2_insert_record ... # 需要执行的业务逻辑 end end
App2的Post模型及创建代码:
class Post < ActiveRecord::Base belongs_to :user end Post.create(name: 'post')
可选方案分析
方案1:消息队列(如Rpush/Sidekiq+Redis)
核心思路:App2创建Post后主动发送消息到队列,App1监听队列并执行对应逻辑。
实现步骤
- 两个应用配置同一消息队列服务(如Rpush);
- App2的
Post模型添加after_create钩子发送消息:class Post < ActiveRecord::Base belongs_to :user after_create :notify_web_app private def notify_web_app Rpush::Notification.create!( app: Rpush::Apns::App.find_by(name: 'web_app'), registration_ids: ['web_app_listener'], data: { post_id: id } ) end end - App1启动后台进程监听队列,收到消息后执行逻辑:
# App1的后台任务 class HandleNewPostJob < ApplicationJob queue_as :default def perform(post_id) post = Post.find(post_id) post.do_stuff_when_app2_insert_record end end # Rpush监听回调 Rpush.configure do |config| config.on_notification_delivered do |notification| HandleNewPostJob.perform_later(notification.data['post_id']) end end
优劣对比
- ✅ 解耦性强,应用间独立;支持高并发;业务逻辑在Rails层,易维护
- ❌ 需要额外维护消息队列服务;存在消息丢失风险(可通过持久化队列缓解)
方案2:SQL触发器
核心思路:在数据库的posts表上创建触发器,插入新记录时触发事件,App1通过轮询或数据库通知感知变化。
实现步骤(以PostgreSQL为例)
- 创建事件记录表:
CREATE TABLE post_events ( id SERIAL PRIMARY KEY, post_id INTEGER REFERENCES posts(id), event_type VARCHAR(20) DEFAULT 'created', created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, processed BOOLEAN DEFAULT FALSE ); - 创建插入触发器及通知函数:
CREATE OR REPLACE FUNCTION notify_post_created() RETURNS TRIGGER AS $$ BEGIN INSERT INTO post_events (post_id) VALUES (NEW.id); PERFORM pg_notify('post_created', NEW.id::TEXT); -- 实时通知 RETURN NEW; END; $$ LANGUAGE plpgsql; CREATE TRIGGER trigger_post_created AFTER INSERT ON posts FOR EACH ROW EXECUTE FUNCTION notify_post_created(); - App1的两种监听方式:
- 定时轮询(简单但有延迟):
# 用whenever gem配置定时任务 every 1.minute do runner "Post.process_new_events" end # Post模型添加处理方法 def self.process_new_events PostEvent.where(processed: false).each do |event| post = Post.find(event.post_id) post.do_stuff_when_app2_insert_record event.update(processed: true) end end - 实时监听pg_notify:
# App1后台进程 require 'pg' conn = PG.connect(dbname: 'DB1') conn.exec("LISTEN post_created") loop do conn.wait_for_notify do |channel, pid, payload| post_id = payload.to_i post = Post.find(post_id) post.do_stuff_when_app2_insert_record end end
- 定时轮询(简单但有延迟):
优劣对比
- ✅ 无需修改应用代码(App2无需改动);实时性可保障
- ❌ 业务逻辑下沉到数据库,维护成本高;触发器出错影响数据库性能;不同数据库语法差异大
方案3:将API逻辑封装为Gem
核心思路:把创建Post的逻辑封装成共用Gem,两个应用依赖该Gem,在Gem中统一处理创建及通知逻辑。
实现步骤
- 创建
post_managementGem,定义统一创建逻辑:module PostManagement class Creator def self.create_post(attributes) post = Post.create(attributes) notify_web_app(post.id) post end private def self.notify_web_app(post_id) # 调用App1的webhook接口 HTTP.post("http://app1.example.com/webhooks/post_created", json: { post_id: post_id }) end end end - App1和App2引入该Gem,App2用Gem方法创建
Post:# App2代码 PostManagement::Creator.create_post(name: 'post') - App1提供webhook接口处理通知:
# App1 routes.rb post '/webhooks/post_created', to: 'webhooks#post_created' # WebhooksController class WebhooksController < ApplicationController skip_before_action :verify_authenticity_token def post_created post_id = params[:post_id] post = Post.find(post_id) post.do_stuff_when_app2_insert_record head :ok end end
优劣对比
- ✅ 业务逻辑统一维护,避免重复;逻辑清晰
- ❌ 需要维护额外Gem;应用版本需同步升级;耦合度较高
方案选择建议
- 优先选消息队列方案:适合大多数生产场景,解耦性和扩展性强;
- 选SQL触发器方案:适合不想改动应用代码,且数据库支持触发器/通知的场景,注意数据库维护风险;
- 选Gem封装方案:适合业务逻辑高度关联、可接受一定耦合度的小型项目,便于快速迭代。
内容的提问来源于stack exchange,提问作者Breno Perucchi
相关产品推荐
相关产品推荐

