大型 Web 应用架构与维护技巧
1. 添加模块变体
在大型 Web 应用中,模块使用场景增多时,往往需要不同的变体。当这些变体需要对模块的 HTML、CSS 和 JavaScript 进行协调更改时,管理难度会增大。不过,合理的模块架构能让处理这些变体变得更轻松,因为模块正常运行所需的一切都封装在模块类中,能清晰了解支持未来变体需要更改的内容。
1.1 处理模块变体的方法
通常,变体需要新的参数来配置模块,但应避免修改已使用模块的代码。有两种处理方法:
- 为模块的构造函数定义额外参数,并将这些参数设为可选。
- 定义 setter 方法,在构造后处理额外参数。
1.2 图片滑块模块示例
以图片滑块模块为例,原始实现如下:
class PictureSlider extends Module
{
var $gallery;
var $type;
var $picture_width;
var $slider_frames;
public function __construct($page, $gallery)
{
parent::__construct($page);
$this->gallery = $gallery;
$this->type = "default";
$this->picture_width = 65;
$this->slider_frames = 8;
}
...
}
1.2.1 使用可选构造函数参数
修改后的构造函数支持新变体:
class PictureSlider extends Module
{
var $gallery;
var $type;
var $picture_width;
var $slider_frames;
public function __construct($page, $gallery, $width="", $frames="")
{
parent::__construct($page);
$this->gallery = $gallery;
$this->type = "default";
if (empty($width))
$this->picture_width = 65;
else
$this->picture_width = $width;
if (empty($frames))
$this->slider_frames = 8;
else
$this->slider_frames = $frames;
}
...
}
这样,现有模块实例不受影响,还能使用新参数创建实例:
$mod = new PictureSlider($this, $gallery, $width, $frames);
$slider = $mod->create();
1.2.2 使用 setter 方法
另一种方法是定义 setter 方法:
class PictureSlider extends Module
{
var $gallery;
var $type;
var $picture_width;
var $slider_frames;
public function __construct($page, $gallery)
{
parent::__construct($page);
$this->gallery = $gallery;
$this->type = "default";
$this->picture_width = 65;
$this->slider_frames = 8;
}
public function set_picture_width($width)
{
$this->picture_width = $width;
}
public function set_slider_frames($frames)
{
$this->slider_frames = $frames;
}
...
}
使用方式如下:
$mod = new PictureSlider($this, $gallery);
$mod->set_picture_width($width);
$mod->set_slider_frames($frames);
$slider = $mod->create();
1.3 基于设置的展示变体
模块的另一种常见变体是基于模块内的设置改变展示效果。通过构造函数传递参数或使用 setter 方法更改设置,利用展示切换应用不同的 CSS 类来实现不同展示。
2. 进行广泛更改
大型 Web 应用生命周期中,进行广泛且一致的更改会越来越难。但合理的架构能让这些更改更易管理。
2.1 页面和模块基类的作用
页面和模块的基类可用于处理广泛更改。例如,设计团队决定为网站某部分页面设置不同的页眉和页脚。通常,整个网站的页面基类
SitePage
会定义返回页眉和页脚 HTML 标记的方法:
class SitePage extends Page
{
...
public function get_header()
{
// Return the HTML markup for the header across the entire site.
return <<<EOD
<div id="sitehdr">
...
</div>
EOD;
}
public function get_footer()
{
// Return the HTML markup for the footer across the entire site.
return <<<EOD
<div id="siteftr">
...
</div>
EOD;
}
...
}
2.2 特定部分页面的页眉和页脚更改
为特定部分定义页面类,重写
get_header
和
get_footer
方法:
class NewCarsPage extends SitePage
{
...
public function get_header()
{
// Return the HTML markup for the header of the New Cars section.
return <<<EOD
<div id="nwchdr">
...
</div>
EOD;
}
public function get_footer()
{
// Return the HTML markup for the footer of the New Cars section.
return <<<EOD
<div id="nwcftr">
...
</div>
EOD;
}
...
}
在
Page
类的
create
方法中,会调用派生类的方法:
class Page
{
...
public function create()
{
...
// These invoke methods of the derived class, if defined there.
$header = $this->get_header();
$content = $this->get_content();
$footer = $this->get_footer();
...
}
...
}
如果特定部分页面类的页眉或页脚需要 CSS 或 JavaScript,可将其放在该部分的 CSS 或 JavaScript 文件中。
2.3 广泛更改的扩展思路
这些思路扩展性强,可在派生自
Page
的基类中定义自己的方法并按需重写。还可直接在多个地方使用的模块中进行更改,模块本身提供了进行广泛更改的中心点。
3. 数据源更改
当一组数据的来源发生变化时,理想情况下应将这种变化从用户界面抽象出来,前提是数据结构保持不变。这体现了使用数据管理器在用户界面和后端系统之间建立明确定义接口的关键优势。
3.1 数据管理器的作用
数据管理器建立的接口就像用户界面和后端之间的一种契约,规定了要交换的数据结构。例如:
array
(
"0" => array
(
"name" => "...",
"price" => "...",
"link" => "..."
),
"1" => array
(
"name" => "...",
"price" => "...",
"link" => "..."
),
"2" => array
(
"name" => "...",
"price" => "...",
"link" => "..."
),
...
)
只要双方遵循数据结构的指导原则,数据来源可以按需更改。
3.2 数据来源更改的示例
3.2.1 新数据提供商
当应用的某个服务有了新的数据提供商,如网站应用开始使用新公司通过数据馈送定期更新新车库存,只需在从馈送中获取数据的数据管理器的
get_data
方法中进行更改。
3.2.2 内容管理系统
在内容管理系统(CMS)中管理数据时,产品经理可通过控制面板控制后端在不同时间检索的内容。为支持预览,可定义一个数据管理器基类,在传递给数据管理器的其他输入参数中存储一个时间参数。通过
GET
、
POST
或 cookie(在 PHP 中通过
$_REQUEST
变量检索)设置预览时间。
以下是相关代码示例:
class CMSDataManager extends DataManager
{
protected $op_environment;
protected $cms_preview_tm;
public function __construct()
{
parent::__construct();
// Set this from a server config that indicates the environment.
$this->op_environment = ...;
if ($this->op_environment != "production" && !empty($_REQUEST["cmstime"]))
{
// Set the timestamp to use for the content management system.
$this->cms_preview_tm = $_REQUEST["cmstime"];
}
else
{
// In production, or if no preview time was given, use the
// current time as the time for which data is to be fetched.
$this->cms_preview_tm = time();
}
}
// No implementations are necessary for get_data and set_data here.
}
class NewCarListingsDataManager extends CMSDataManager
{
public function __construct()
{
parent::__construct();
...
}
public function get_data($load_args, &$load_data, &$load_stat)
{
// Explicitly add the timestamp for the content management system.
$load_args["cms_preview_tm"] = $this->cms_preview_tm;
// Do whatever else you would normally do to retrieve the data.
// The timestamp is passed automatically with the other arguments
// so that it can be used as needed to fetch time-sensitive data.
...
}
...
}
这样,基于预览时间在数据源内的更改就从用户界面中抽象出来了。预览通常在非生产环境中可用,预览时访问的后端系统通常与生产环境不同,以保持两个环境中的数据隔离。
4. 外部暴露模块
虽然某些类设计为协同工作,但它们也能独立工作。用于在派生自
Page
的页面上创建模块的相同接口,可提取模块所需的一切(如 HTML、CSS 和 JavaScript),以便将其放置在不同环境生成的页面上。
4.1 模块外部使用示例
// You do need some instance of Page for modules, but then ignore it.
$page = new TransPage();
...
// Anyone can extract a module's HTML, CSS, and JavaScript like this.
$mod = new PictureSlider($page, ...);
$html = $mod->create();
$css_linked = $mod->get_css_linked();
$css = $mod->get_css();
$js_linked = $mod->get_js_linked();
$js = $mod->get_js();
模块构造函数需要一个
Page
实例,但构造后可忽略该页面。为了更高效,可定义一个特殊的页面类(如
TransPage
),对模块调用但在此上下文中不需要的方法进行空实现。
4.2 模块化架构的优势
这使得可以以渐进的方式构建系统。大型 Web 应用的构建和修改需要大量时间,拥有真正的模块化架构很重要,它有可能在不同部分处于不同状态时在其他系统中工作。
总结
本文介绍了大型 Web 应用开发中处理模块变体、进行广泛更改、应对数据源变化以及外部暴露模块的方法和技巧。通过合理的架构设计和代码实现,能提高应用的可维护性、可扩展性和灵活性。在实际开发中,可根据具体需求选择合适的方法,确保应用在不同场景下都能稳定运行。
以下是一个简单的流程图,展示了处理模块变体的两种方法:
graph LR
A[模块变体需求] --> B{选择处理方法}
B -->|可选构造函数参数| C[修改构造函数]
B -->|setter 方法| D[定义 setter 方法]
C --> E[创建实例时使用新参数]
D --> F[构造后使用 setter 方法设置参数]
同时,为了更清晰地对比两种处理模块变体的方法,我们列出以下表格:
| 处理方法 | 优点 | 缺点 | 适用场景 |
| — | — | — | — |
| 可选构造函数参数 | 代码简洁,创建实例时可一次性设置参数 | 构造函数参数可能过多,影响可读性 | 变体参数较少且创建实例时就需确定参数的情况 |
| setter 方法 | 灵活性高,可在构造后随时设置参数 | 代码量稍多,需要额外的方法调用 | 变体参数较多或需要在不同阶段设置参数的情况 |
5. 架构相关的其他要点
5.1 缓存策略
在大型 Web 应用中,缓存对于提高性能至关重要。以下是不同方面的缓存策略:
-
CSS 和 JavaScript 缓存
:可以通过版本控制和文件合并来实现。例如,为 CSS 和 JavaScript 文件添加日期戳或版本号,确保浏览器在文件更新时重新加载。同时,将多个 CSS 或 JavaScript 文件合并为一个,减少 HTTP 请求。
-
页面缓存
:对于不经常变化的页面,可以进行页面级别的缓存。可以使用服务器端的缓存机制,如 APC(Alternative PHP Cache),来存储页面的 HTML 内容,减少每次请求时的处理时间。
-
模块缓存
:对于经常使用的模块,也可以进行缓存。可以在模块类中实现缓存方法,根据一定的规则(如时间戳、数据变化等)来判断是否需要重新生成模块内容。
5.2 数据管理
数据管理在 Web 应用中起着关键作用。以下是数据管理的一些要点:
-
数据管理器
:使用数据管理器来封装数据的访问和处理逻辑。数据管理器可以连接到不同的数据源,如数据库、XML 文件等,并提供统一的接口供其他模块使用。
-
数据接口
:定义清晰的数据接口,确保不同模块之间的数据交互顺畅。数据接口可以规定数据的格式、类型和操作方法,提高代码的可维护性和可扩展性。
-
数据缓存
:在数据管理器中实现数据缓存机制,减少对数据源的频繁访问。可以使用内存缓存或文件缓存来存储经常使用的数据。
5.3 性能优化
为了提高 Web 应用的性能,可以采取以下措施:
-
减少 HTTP 请求
:合并 CSS 和 JavaScript 文件,使用图片精灵(spriting)技术减少图片请求,优化 DNS 查找等。
-
优化代码
:对 HTML、CSS 和 JavaScript 代码进行压缩和优化,去除不必要的空格、注释和冗余代码。
-
使用 CDN(内容分发网络)
:将静态资源(如 CSS、JavaScript、图片等)分发到 CDN 上,减少服务器的负载和用户的访问延迟。
以下是一个性能优化的流程图:
graph LR
A[性能优化需求] --> B{选择优化方向}
B -->|减少 HTTP 请求| C[合并文件、使用图片精灵等]
B -->|优化代码| D[压缩代码、去除冗余]
B -->|使用 CDN| E[配置 CDN 服务]
C --> F[测试性能]
D --> F
E --> F
F -->|性能达标| G[结束优化]
F -->|性能未达标| B
6. 代码示例总结
为了更好地理解上述内容,我们对前面提到的代码示例进行总结:
6.1 图片滑块模块
- 原始实现 :
class PictureSlider extends Module
{
var $gallery;
var $type;
var $picture_width;
var $slider_frames;
public function __construct($page, $gallery)
{
parent::__construct($page);
$this->gallery = $gallery;
$this->type = "default";
$this->picture_width = 65;
$this->slider_frames = 8;
}
...
}
- 使用可选构造函数参数 :
class PictureSlider extends Module
{
var $gallery;
var $type;
var $picture_width;
var $slider_frames;
public function __construct($page, $gallery, $width="", $frames="")
{
parent::__construct($page);
$this->gallery = $gallery;
$this->type = "default";
if (empty($width))
$this->picture_width = 65;
else
$this->picture_width = $width;
if (empty($frames))
$this->slider_frames = 8;
else
$this->slider_frames = $frames;
}
...
}
- 使用 setter 方法 :
class PictureSlider extends Module
{
var $gallery;
var $type;
var $picture_width;
var $slider_frames;
public function __construct($page, $gallery)
{
parent::__construct($page);
$this->gallery = $gallery;
$this->type = "default";
$this->picture_width = 65;
$this->slider_frames = 8;
}
public function set_picture_width($width)
{
$this->picture_width = $width;
}
public function set_slider_frames($frames)
{
$this->slider_frames = $frames;
}
...
}
6.2 页面页眉和页脚
- 全站页面基类 :
class SitePage extends Page
{
...
public function get_header()
{
// Return the HTML markup for the header across the entire site.
return <<<EOD
<div id="sitehdr">
...
</div>
EOD;
}
public function get_footer()
{
// Return the HTML markup for the footer across the entire site.
return <<<EOD
<div id="siteftr">
...
</div>
EOD;
}
...
}
- 特定部分页面类 :
class NewCarsPage extends SitePage
{
...
public function get_header()
{
// Return the HTML markup for the header of the New Cars section.
return <<<EOD
<div id="nwchdr">
...
</div>
EOD;
}
public function get_footer()
{
// Return the HTML markup for the footer of the New Cars section.
return <<<EOD
<div id="nwcftr">
...
</div>
EOD;
}
...
}
6.3 数据管理器
- CMS 数据管理器基类 :
class CMSDataManager extends DataManager
{
protected $op_environment;
protected $cms_preview_tm;
public function __construct()
{
parent::__construct();
// Set this from a server config that indicates the environment.
$this->op_environment = ...;
if ($this->op_environment != "production" && !empty($_REQUEST["cmstime"]))
{
// Set the timestamp to use for the content management system.
$this->cms_preview_tm = $_REQUEST["cmstime"];
}
else
{
// In production, or if no preview time was given, use the
// current time as the time for which data is to be fetched.
$this->cms_preview_tm = time();
}
}
// No implementations are necessary for get_data and set_data here.
}
- 特定数据管理器 :
class NewCarListingsDataManager extends CMSDataManager
{
public function __construct()
{
parent::__construct();
...
}
public function get_data($load_args, &$load_data, &$load_stat)
{
// Explicitly add the timestamp for the content management system.
$load_args["cms_preview_tm"] = $this->cms_preview_tm;
// Do whatever else you would normally do to retrieve the data.
// The timestamp is passed automatically with the other arguments
// so that it can be used as needed to fetch time-sensitive data.
...
}
...
}
7. 总结与展望
通过以上内容,我们了解了大型 Web 应用开发中架构和维护的多个方面,包括模块变体处理、广泛更改、数据源变化应对、外部暴露模块以及缓存、数据管理和性能优化等。这些技术和方法能够帮助我们构建更健壮、可维护和高性能的 Web 应用。
在未来的开发中,随着 Web 技术的不断发展,我们还需要不断学习和探索新的架构模式和优化方法。例如,随着前端框架(如 React、Vue.js 等)的兴起,我们可以将其与后端的架构相结合,实现更高效的开发和更好的用户体验。同时,随着云计算和大数据的发展,我们也需要考虑如何在分布式环境中进行数据管理和性能优化。
总之,大型 Web 应用的开发是一个不断演进的过程,需要我们不断地学习和实践,以适应不断变化的技术和业务需求。
以下是一个表格,总结了不同方面的关键要点:
| 方面 | 关键要点 |
| — | — |
| 模块变体 | 使用可选构造函数参数或 setter 方法处理,基于设置改变展示效果 |
| 广泛更改 | 利用页面和模块基类,重写方法实现特定部分的更改 |
| 数据源变化 | 使用数据管理器抽象变化,支持预览功能 |
| 外部暴露模块 | 提取模块所需内容,放置在不同环境页面上 |
| 缓存策略 | 对 CSS、JavaScript、页面和模块进行缓存 |
| 数据管理 | 使用数据管理器封装数据访问,定义清晰接口 |
| 性能优化 | 减少 HTTP 请求,优化代码,使用 CDN |
大型Web应用架构与维护
超级会员免费看
7474

被折叠的 条评论
为什么被折叠?



