打破规则:使用 SQLite 演示 Web 应用程序
已发表: 2022-03-10在投入任何时间和金钱之前,大多数潜在用户都希望试用该软件或服务。 一些产品只需为用户提供免费试用就可以很好地发挥作用,而其他应用程序则在已有样本数据的情况下体验最佳。 这通常是古老的模拟账户发挥作用的地方。
但是,任何曾经实施过模拟账户的人都可以证明相关的问题。 您知道互联网上的事情是如何运行的:任何人都可以输入数据(无论对产品是否有意义),并且匿名用户或机器人添加的内容很可能会冒犯他人。 当然,您可以随时重置数据库,但多久重置一次? 最终,这真的能解决问题吗? 我使用 SQLite 的解决方案。
为什么不将 SQLite 用于生产版本?
众所周知,SQLite 不处理多个线程,因为在写入命令期间整个数据库被锁定,这也是您不应在正常生产环境中使用它的原因之一。 但是,在我的解决方案中,每个演示软件的用户使用一个单独的 SQLite 文件。 这意味着写入限制仅限于该一个用户,但多个同时用户(每个用户都有自己的数据库文件)将不会遇到此限制。 这为用户测试驾驶软件提供了可控的体验,并使他们能够准确地查看您希望他们看到的内容。
本教程基于我自 2015 年以来为 SaaS 演示 Web 应用程序成功运行的真实解决方案。本教程是为 Ruby on Rails(我选择的框架)版本 3 及更高版本编写的,但基本概念应该是能够适应任何其他语言或框架。 事实上,由于 Ruby on Rails 遵循“约定优于配置”的软件范式,它甚至可能更容易在其他框架中实现,尤其是裸语言(例如直接 PHP)或在管理数据库连接方面做得不多的框架.
话虽如此,这种技术特别适合 Ruby on Rails。 为什么? 因为,在大多数情况下,它是“与数据库无关的”。 这意味着您应该能够编写 Ruby 代码并在数据库之间切换而不会出现任何问题。
可以从 GitHub 下载此过程的完成版本的示例。
第一步:部署环境
我们稍后会进行部署,但 Ruby on Rails 默认分为开发、测试和生产环境。 我们将为我们的应用程序添加一个新的演示环境,该环境几乎与生产环境相同,但允许我们使用不同的数据库设置。
在 Rails 中,通过复制config/environments/production.rb
文件并重命名为demo.rb
来创建一个新环境。 由于演示环境将用于类似生产环境的设置,因此您可能不需要为这个新环境更改许多配置选项,但我建议将config.assets.compile
从false
更改为true
,这样可以更轻松地在本地进行测试,而无需必须预编译。
如果您运行的是 Rails 4 或更高版本,您还需要更新config/secrets.yml
secrets.yml 为演示环境添加一个secret_key_base
。 请务必使此密钥与生产环境不同,以确保每个环境之间的会话都是唯一的,从而进一步保护您的应用程序。
接下来,您需要在config/database.yml
中定义数据库配置。 虽然演示环境将主要使用我们将在下一节中介绍的复制数据库,但我们必须定义用于演示的默认数据库文件和设置。 将以下内容添加到config/database.yml
:
demo: adapter: sqlite3 pool: 5 timeout: 5000 database: db/demo.sqlite3
在 Rails 中,您可能还需要检查Gemfile
以确保 SQLite3 在新的演示环境中可用。 您可以通过多种方式设置它,但它可能如下所示:
group :development, :test, :demo do gem 'sqlite3' end
配置数据库后,您需要rake db:migrate RAILS_ENV=demo
,然后根据需要将数据播种到数据库中(无论是来自种子文件、手动输入新数据还是复制development.sqlite3
文件)。 此时,您应该通过从命令行运行rails server -e demo
来检查以确保一切正常。 当您在新的演示环境中运行服务器时,您可以确保您的测试数据是您想要的,但您可以随时回来编辑该内容。 将内容添加到演示数据库时,我建议创建一组干净的数据,以使文件尽可能小。 但是,如果您需要从另一个数据库迁移数据,我推荐使用 YamlDb,它创建了一种独立于数据库的格式,用于转储和恢复数据。
如果您的 Rails 应用程序按预期运行,您可以继续下一步。
第二步:使用演示数据库
本教程的重要部分是能够允许每个会话使用不同的 SQLite 数据库文件。 通常,您的应用程序将为每个用户连接到同一个数据库,因此此任务需要额外的代码。
要开始允许 Ruby on Rails 切换数据库,我们首先需要将以下四个私有方法添加到application_controller.rb
中。 您还需要为方法set_demo_database
定义一个 before 过滤器,以便在每次页面加载时调用引用正确演示数据库的逻辑。
# app/controllers/application_controller.rb # use `before_filter` for Rails 3 before_action :set_demo_database, if: -> { Rails.env == 'demo' } private # sets the database for the demo environment def set_demo_database if session[:demo_db] # Use database set by demos_controller db_name = session[:demo_db] else # Use default 'demo' database db_name = default_demo_database end ActiveRecord::Base.establish_connection(demo_connection(db_name)) end # Returns the current database configuration hash def default_connection_config @default_config ||= ActiveRecord::Base.connection.instance_variable_get("@config").dup end # Returns the connection hash but with database name changed # The argument should be a path def demo_connection(db_path) default_connection_config.dup.update(database: db_path) end # Returns the default demo database path defined in config/database.yml def default_demo_database return YAML.load_file("#{Rails.root.to_s}/config/database.yml")['demo']['database'] end
由于每个服务器会话都有不同的数据库,因此您将数据库文件名存储在会话变量中。 如您所见,我们使用session[:demo_db]
来跟踪用户的特定数据库。 set_demo_database
方法通过建立与会话变量中设置的数据库的连接来控制要使用的数据库。 default_demo_database
方法只是加载database.yml
配置文件中定义的数据库路径。
如果您使用的是纯语言,此时您可能只需更新数据库连接脚本以指向新数据库,然后继续下一部分。 在 Rails 中,事情需要更多的步骤,因为它遵循“约定优于配置”的软件范例。
第三步:复制 SQLite 文件
现在应用程序已设置为使用新数据库,我们需要一个新演示会话的触发器。 为简单起见,首先使用一个基本的“开始演示”按钮。 您还可以将其设置为收集姓名和电子邮件地址(用于销售团队的跟进等)或任何数量的表格。
坚持 Rails 约定,创建一个新的 'Demo' 控制器:
rails generate controller demos new
接下来,您应该更新路由以指向您的新控制器操作,将它们包装在条件中以防止它在生产环境中被调用。 您可以根据需要命名路线或使用标准 Rails 约定命名它们:
if Rails.env == 'demo' get 'demos/new', as: 'new_demo' post 'demos' => 'demos#create', as: 'demos' end
接下来,让我们在views/demos/new.html.erb
中添加一个非常基本的表单。 您可能需要添加其他表单字段来捕获:
<h1>Start a Demo</h1> <%= form_tag demos_path, method: :post do %> <%= submit_tag 'Start Demo' %> <% end %>
魔法发生在create
动作中。 当用户提交到该路由时,该操作将使用新的唯一文件名复制demo.sqlite3
文件,设置会话变量,登录用户(如果适用),然后将用户重定向到相应的页面(我们将其称为'仪表板')。
class DemosController < ApplicationController def new # Optional: setting session[:demo_db] to nil will reset the demo session[:demo_db] = nil end def create # make db/demos dir if doesn't exist unless File.directory?('db/demos/') FileUtils.mkdir('db/demos/') end # copy master 'demo' database master_db = default_demo_database demo_db = "db/demos/demo-#{Time.now.to_i}.sqlite3" FileUtils::cp master_db, demo_db # set session for new db session[:demo_db] = demo_db # Optional: login code (if applicable) # add your own login code or method here login(User.first) # Redirect to wherever you want to send the user next redirect_to dashboard_path end end
现在,您应该能够再次使用运行rails server -e demo
启动服务器来在本地试用演示代码。
如果您的服务器已经在运行,您将需要重新启动它以进行任何更改,因为它已配置为像生产服务器一样缓存代码。
一旦所有代码按预期工作,提交您对版本控制的更改并确保提交demo.sqlite3
文件,而不是db/demos
目录中的文件。 如果您使用的是 git,您只需将以下内容添加到您的.gitignore
文件中:
如果您想从演示用户那里收集其他信息(例如姓名和/或电子邮件),您可能希望通过 API 将该信息发送到您的主应用程序或其他一些销售渠道,因为您的演示数据库将不可靠(每次重新部署时都会重置)。
!/db/demo.sqlite3 db/demos/*
最后一步:部署演示服务器
现在您的演示设置在本地工作,您显然希望部署它以便每个人都可以使用它。 虽然每个应用程序都不同,但我建议演示应用程序位于单独的服务器上,因此域作为您的生产应用程序(例如 demo.myapp.com)。 这将确保您保持两个环境隔离。 此外,由于 SQLite 文件存储在服务器上,因此像 Heroku 这样的服务将无法工作,因为它不提供对文件系统的访问。 但是,您仍然可以使用几乎任何 VPS 提供商(例如 AWS EC2、Microsoft Azure 等)。 如果您喜欢自动化的便利,还有其他平台即服务选项可让您使用 VPS。
无论您的部署过程如何,您可能还需要检查应用程序是否对您存储演示 SQLite 文件的目录具有适当的读/写权限。 这可以手动或使用部署挂钩来处理。
SQLite 不适合我。 其他数据库系统呢?
没有两个应用程序是相同的,它们的数据库要求也没有。 通过使用 SQLite,您具有能够快速复制数据库以及能够将文件存储在版本控制中的优势。 虽然我相信 SQLite 将适用于大多数情况(尤其是使用 Rails),但在某些情况下 SQLite 可能不适合您的应用程序的需求。 幸运的是,仍然可以将上述相同的概念用于其他数据库系统。 对于每个系统,复制数据库的过程会略有不同,但我将概述 MySQL 的解决方案,PostgreSQL 和其他系统也存在类似的过程。
上面介绍的大多数方法都可以在没有任何额外修改的情况下工作。 但是,不要在版本控制中存储 SQLite 文件,而应使用mysqldump
(或 PostgreSQL 的pg_dump
)导出包含您想要用于演示体验的内容的任何数据库的 SQL 文件。 该文件也应该存储在您的版本控制中。
对先前代码的唯一更改将在demos#create
操作中找到。 控制器操作不会复制 SQLite3 文件,而是创建一个新数据库,将 sql 文件加载到该数据库中,并在必要时为数据库用户授予权限。 仅当您的数据库管理员用户与应用程序用于连接的用户不同时,才需要授予访问权限的第三步。 以下代码使用标准 MySQL 命令来处理这些步骤:
def create # database names template_demo_db = default_demo_database new_demo_db = "demo_database_#{Time.now.to_i}" # Create database using admin credentials # In this example the database is on the same server so passing a host argument is not require `mysqladmin -u#{ ENV['DB_ADMIN'] } -p#{ ENV['DB_ADMIN_PASSWORD'] } create #{new_demo_db}` # Load template sql into new database # Update the path if it differs from where you saved the demo_template.sql file `mysql -u#{ ENV['DB_ADMIN'] } -p#{ ENV['DB_ADMIN_PASSWORD'] } #{new_demo_db} < db/demo_template.sql` # Grant access to App user (if applicable) `mysql -u#{ ENV['DB_ADMIN'] } -p#{ ENV['DB_ADMIN_PASSWORD'] } -e "GRANT ALL on #{new_demo_db}.* TO '#{ ENV['DB_USERNAME'] }'@'%';"` # set session for new db session[:demo_db] = new_demo_db # Optional: login code (if applicable) # add your own login code or method here login(User.first) redirect_to dashboard_path end
与包括 PHP 在内的许多其他语言一样,Ruby 允许您在代码中使用反引号来执行 shell 命令(即`ls -a`
)。 但是,您必须谨慎使用它,并确保不能将面向用户的参数或变量插入命令中,以保护您的服务器免受恶意注入的代码的侵害。 在这个例子中,我们显式地与 MySQL 命令行工具交互,这是创建新数据库的唯一方法。 这与 Ruby on Rails 框架创建新数据库的方式相同。 请务必将ENV['DB_ADMIN']
和ENV['DB_ADMIN_PASSWORD']
替换为您自己的环境变量或任何其他设置数据库用户名的方式。 如果您的管理员用户与您的应用程序的用户不同,您将需要对ENV['DB_USERNAME']
执行相同的操作。
这就是切换到 MySQL 所需的全部内容! 此解决方案最明显的优势是您不必担心数据库系统之间的不同语法可能出现的潜在问题。
最终,最终决定是基于预期的质量和服务,而不是便利性和速度,并且不一定仅受价格点的影响。
最后的想法
这只是您可以使用新演示服务器执行的操作的起点。 例如,您的营销网站可能有一个指向“试用功能 XYZ”的链接。 如果您不需要姓名或电子邮件,则可以将demos#create
方法与/demos/?feature=xyz
之类的链接链接起来,该操作将简单地重定向到所需的功能和/或页面,而不是中的仪表板上面的例子。
此外,如果您将 SQLite 用于开发和演示环境,则始终在版本控制中使用此示例数据库将使您的所有开发人员都可以访问干净的数据库,以便在本地开发、测试环境或质量保证测试中使用。 可能性是无止境。
您可以从 GitHub 下载完整的演示。