让upGrad.com走向国际:一个关于我们如何构建支持体系的故事
几个月前,upGrad决定拓展国际市场,这要求我们在各个面向客户的技术产品中支持这一扩张。第一个需要进行重大改造的就是课程列表网站——upgrad.com。在这篇文章中,我将介绍我们是如何规划和实施这项改造的,以及我们在此过程中学到的经验。
国际化第一阶段的期望
对于国际化的第一阶段,平台需要达成的目标是:内部团队应该能够在不同国家独立推广upGrad的课程。这意味着每个国家显示的内容可以不同,并且能够分别维护。此外,当访问者试图打开upgrad.com网站上的任何页面时,应自动重定向到他们国家特定的URL。这是许多网站采用的通用行为,内容通过附加在基础URL后的特定代码进行区分。
一个可能不太明显的额外要求是:在整个访问者会话期间保持这个附加的代码。如果用户访问的是upgrad.com/us,那么所有后续的路由处理都应该在初始位置代码的上下文中进行。例如,如果访问者点击了一个导致路由跳转到 /data-science-pgd-iiitb 的超链接或按钮,那么最终的URL应该是 upgrad.com/us/data-science-pgd-iiitb。
总而言之,我们需要解决三个问题:
- 支持在数据库中保存特定地区的页面并允许分别编辑。
- 处理客户端路由。
- 根据用户位置自动重定向到这些特定页面。
更新CMS以处理国际化页面
我们有一个内部内容管理系统(CMS),名为Apollo,用于管理upgrad.com上的内容。‘页面’可以被描述为数据库中映射到唯一slug的文档。之前,我们只能使用单层slug,它直接映射到upgrad.com上的URL。例如,data-science-pgd-iiitb映射到upgrad.com/data-science-pgd-iiitb。为了让页面支持像upgrad.com/us/data-science-pgd-iiitb这样的双层URL,我们在页面文档中引入了一个名为i18n的新属性。这是一个对象,它保存了locale的值,locale是一个分配给每个国家的唯一两位代码。地区代码是很好的国家文本标识符,可以直接映射到URL。
在创建页面时,编辑可以指定他们希望为哪个国家创建页面,该值将在页面创建后附加到页面文档中。我们将之前用于页面的slug标识符分成了两部分,即name和i18n.locale。
|
|
name属性将包含页面所属课程的逻辑名称。这可以用于后续将所有具有相同name的页面进行分组。如上文代码片段所示,slug属性现在包含了文档所代表的完整URL。也就是说,如果请求美国的/data-science-pgd-iiitb页面,我可以期望从上述文档中获取数据。然而,页面数据加载逻辑并非如此简单,我将在后面谈到。
你可能会问,为什么我们要在name、slug和i18n.locale中维护重复的信息。答案很简单——为了简化各种获取场景。在此项目中,我们希望尽可能减少破坏性变更,并在可能的地方提供回退机制。我们内部称为Apollo的CMS之前仅被Web客户端使用。但在引入upGrad的移动应用程序后,它被扩展到了Android和iOS移动客户端。因此,保持CMS API端点和响应模式不变至关重要。
回退机制
为了能够覆盖尽可能多的地区,同时保持较小的数据占用,国际化营销页面遵循了区域性的回退机制。如果简单地考虑,为每个国家的每个课程创建一个页面将意味着我们的数据会以m * n的规模增长。这也意味着内容维护人员为了修改某个特定课程的所有国家页面,将不得不更新多个页面。这并不是一个理想的方案。
为了解决这个问题,我们的想法是维护最少数量的国际化页面,并将这些少量页面的相关内容提供给全球所有地区。这意味着我们只需要维护少量页面,并且可以动态更新应为特定地区提供哪个页面。
概括来说,我们在API中引入了一个配置对象。这是一个JSON结构,用于保存按区域分组的位置信息。逻辑很简单:这棵树中叶节点的值代表各个国家/地区,它们可能与一个页面相关联。如果没有关联的页面,页面获取API将向上移动一级,并尝试递归地获取与该地区关联的页面。
例如,如果客户端请求地区代码为ae的页面,我们会检查ae页面是否存在;如果不存在,则检查asia页面是否存在;如果还是不存在,则返回所请求实体的全局页面。
|
|
引入这个机制使我们能够将国家关联到特定区域,从而可以向该区域下的每个国家提供区域数据。为了避免这个配置变得过于庞大,我们在自身层面上确保每个页面都存在一个全局页面变体。这确保了如果请求一个不存在于我们配置中的国家的页面,它总是会回退到全局页面。
页面端点现在接受一个locale参数,客户端可以通过它指定需要哪个国家的页面。这主要涵盖了大部分兼容性问题,特别是在逐步更新移动客户端时,因为端点保持不变,即使没有发送locale查询参数,API也默认假设为in(印度),这是国际化之前的情况。
处理客户端路由
我们使用Nuxt.js的通用模式来提供我们的营销页面。幸运的是,Nuxt(以及整个Vue社区)为常见用例提供了一套官方模块,包括国际化。多亏了nuxt/i18n模块,我们的很多工作得以简化。该模块提供了启动时静态路由的自动生成、客户端路由,并提供了多种配置方式。
在很多地方,我们都在将用户重定向到应用内或应用外的URL。使用i18n时,这必须通过一个转换函数来执行,该函数将当前的locale值附加到新的URL上。nuxt/i18n为此公开了一个名为localePath的函数。我们发现这是一个很好的机会,可以清理我们现有的重定向逻辑,并通过编写一个包装函数将其统一到一处,该包装函数重定向到URL,并在URL是内部链接时内部可选地使用localePath。
当应用启动时,nuxt/i18n会从URL中保存与当前地区相关的数据。例如,upgrad.com/us/data-science-pgd-iiitb会将地区值us及其相关信息存储到store中。利用这个值,Nuxt应用会向CMS API发起调用,并使用由该模块设置的locale查询参数。之后,API负责返回精确匹配的数据,或者应用某种回退机制来检索最接近的匹配数据。一旦数据返回,Nuxt应用就会根据数据中指定的布局进行渲染。
自动重定向到国家特定URL
这是整个项目中最具美感的特性。无论后台进行了多少数据库和API的更改,用户只需看到URL神奇地变成了他们所在地区的代码,就会感到惊喜。但我们开发者知道,为了实现这一点并确保平台其他部分像以前一样正常运行,背后付出了多少努力。
我们现有的营销网站通过CloudFront提供服务,以实现边缘缓存来降低延迟。我们决定利用这个设置,增加一步路由到地区URL的功能。重申一下当前的问题:当一位查看者(比如来自美国)打开upgrad.com/data-science-pgd-iiitb时,请求应被重定向到upgrad.com/us/data-science-pgd-iiitb。
这是一个完美的按需执行函数的用例——AWS Lambda。AWS Lambda有一个CloudFront变体叫做Lambda@Edge,它创建一个函数的副本并将其部署在CloudFront分发网络边缘。然后可以将其配置为在某些触发事件时执行。
如上图所示,我们将Lambda函数放在origin request触发器上。当CloudFront向源服务器请求内容时(函数位于CloudFront和源服务器之间),会触发此事件。Lambda的职责是创建重定向或将URL传递给源服务器。为了检测用户位置,CloudFront的国家标头派上了用场。此外,我们还使用了由MaxMind IP数据库支持的内部位置解析API。
要访问CloudFront的国家标头,需要将其添加到行为配置的白名单标头中。你可以通过名称CloudFront-Viewer-Country找到它。
用Node.js实现的Lambda函数大致如下:
|
|
一旦这个Lambda被发布到边缘位置,源请求就开始流经这个lambda函数,从世界各地访问网站的用户看到了不同的内容。这是整个流程的最后一步(也是用户旅程的第一步),如果我们一步步回溯,所有环节都衔接得天衣无缝。
当用户从美国打开upgrad.com网站时,lambda检测位置并分配地区值us,然后在请求到达源服务器(Nuxt应用)之前重定向到/us。nuxt/i18n模块接管并将Nuxt上下文中的地区值设置为us,这导致附带相应查询参数的API请求被触发。API服务器尝试在数据库中查找美国页面,并将最接近的匹配文档返回给客户端。收到数据后,客户端成功渲染页面并将其返回给CloudFront。CloudFront然后将此结果返回给查看者,并同时针对原始请求URL和重定向后的URL进行缓存。就是这样!
未来展望
这是营销网站国际化的第一阶段,现在已经能够在多个国家推广我们的课程,我们能够在各地独立运营内容。接下来的步骤将包括添加本地语言支持和允许手动更改区域。此外,我们认为在Lambda方法上还可以进行改进。目前,跳过某些不符合此条件的URL的逻辑有点分散。我们正在想办法使其更加集中,以便可以通过单一事实来源进行配置。如果你在你的应用中实现了类似的东西,请在评论中告诉我们,很乐意了解不同的观点。
如果没有整个团队的成功合作,这个项目是不可能完成的。向Maitrey、Jitesh、Shahir、Abhishek、Harshita、Omkar、Akshay、Vishal、Yuvaraj及其团队,以及所有做出贡献的人致以敬意。请访问upGrad.com查看我们完全在线的课程!如果你想与我们充满热情的团队一起工作,请查看我们的招聘页面。我们一直在寻找有抱负、有才华的人!
参考资料与延伸阅读
- Handling Redirects@Edge Part 1 & 2
- Personalize Content by Country or Device Type Headers — Examples
- I18n
- Lambda Edge
- Cloudfront
- Nuxtjs
- Upgrad