我之前写过一篇关于作为高级软件工程师构建TradePoint移动应用的经历的文章。然而,在那次旅程开始之前,我参与了将B&Q应用交付到应用商店的项目。由于在2024年夏天我们忙于完成新的B&Q应用的交付,因此我没能更早地分享这个故事。请将这篇文章视为我TradePoint文章的前传。
《重建——这一切是怎么开始的》直到2022年底,Kingfisher PLC已经在iOS和Android的应用商店上有了B&Q的应用程序。我们的最初需求似乎很简单:为[B&Q的贸易部门]TradePoint开发两个新的应用程序,这个部门之前并没有自己的应用。然而,到了2023年初,项目已经过去12周,一切都变了。按照软件开发惯例,我们改变了方向。新的方向是:先重建B&Q的应用,然后再为TradePoint开发,正如丁尼生所写的:“不问为什么,只管去做”,对于这种转变,我完全赞同。
Kingfisher PLC 移动应用简史2016年首次构建的B&Q的移动应用,已经发展成为一种白标解决方案,可以在Kingfisher在法国和波兰的[Castorama]品牌中共享。这些应用既展示了集中化、多地域方法的优势,也暴露了其劣势。一方面,这种白标解决方案提供了一致性和成本效益的基础条件,使多个市场可以共享基础功能和资源。然而,这种一刀切的架构越来越难以适应欧盟内不同市场的特定需求。
为特定地区定制功能往往涉及漫长的开发周期和复杂的审批程序,因为所有更改都需要与其他国家使用的更广泛的框架保持一致。这种集中式模型难以平衡来自不同地理市场的利益相关者的需求。例如,受到波兰克拉科夫客户欢迎的功能可能与英国莱明顿花园镇的客户期望不符,这会在优先级设定和交付过程中产生冲突。
随着客户期望的演变,架构的僵化极大地阻碍了创新。简单的改动变得过于昂贵且耗时,使得B&Q应用程序无法跟上市场快速变化的需求。最终,这表明了在集中的开发功能中管理多样化利益相关者需求的内在挑战,其中妥协往往阻碍了地方响应和市场特定创新的能力。
小组去中心化策略希望从完全集中的模式转向更分散的技术运营模式,这标志着我们在Kingfisher旗下各品牌(包括B&Q)应用开发管理上的一个关键转变。虽然集中的模式在成本效益和一致性方面可能更有优势,但它也存在局限,往往难以适应不同市场的独特需求。像B&Q这样的本地品牌难以迅速调整其应用以更好地服务英国客户的特定需求。
在集中式系统下,所有的发展决策都通过核心团队进行,导致了较长的发展周期和不同市场间的优先级冲突。来自不同地区的相关方经常争夺资源,导致了妥协,这稀释了针对个别市场的解决方案的效果。
去中心化提供了一个解决方法。通过赋予地方分店自主权来优先发展他们自己的数字策略,B&Q 可以针对英国市场定制功能和更新。这种方法使团队能够迅速响应客户反馈,尝试新的想法,并更快地迭代,从而培养了一种拥有感和创新精神的文化。例如,B&Q 可以独立推出改进的产品搜索或简化结账流程等功能,而无需等待与其他分店的协调。
独立的代价是什么为了取得平衡,我们探索了一种混合模型。平台的核心元素,如通过Kotlin多平台共享的业务逻辑和GraphQL API,保持中心化以确保一致性和效率。同时,UI开发和市场特定功能的开发则本地化处理,以实现差异化和更快的执行。这种混合方法会结合两者的优势:在集团层面保留效率,同时允许各个业务部门或业务单元在其各自的市场中进行创新和取得卓越。
要为什么要重建?一场技术革新的故事2016年的B&Q应用程序是时代的产物。这些应用当时使用了早期的Swift和遗留的Objective-C代码开发iOS端,而Android部分则使用了Java和Kotlin。维护这些应用需要大量的手动维护。例如,创建一个基本的产品卡片界面就需要在多个文件和冗长的代码之间进行调整和协调。开发人员还必须应对Android端的XML布局和iOS端的Interface Builder,这些都增加了额外的复杂性层次。
然而,到了2022年,行业已经向前迈进。SwiftUI、Jetpack Compose 和 KMP 不仅被视为趋势,更是被证明是真正的游戏规则改变者。这些现代框架承诺了更快的开发、更简洁的代码和更高的灵活性。有了这些工具在手,重建 B&Q 应用程序不再只是升级——而是为我们的移动平台做好未来的准备。
这并不是一个我们轻易做出的决定。重建一个应用需要投入大量的时间和资源,是一项重大投资。潜在的好处,从更快的功能交付到更佳的用户体验,这使得我们的选择非常明确。
瑞典电商应用剖析我谈到了电子商务应用的DNA,接下来我将探讨零售应用中常见的功能和能力。
深入了解你要交付软件的业务领域是非常明智的建议,虽然这听起来显而易见,但这仍然让我感到惊讶,很多开发人员在其 IT 职业生涯中仍然未能完全理解他们所处的业务生态系统内。
一个成功的零售APP远不止是简单的品牌推广。多个因素对于打造无缝购物体验至关重要。让我们来看看构成现代零售APP的关键功能。
主屏幕:数字店面入口主屏幕通常是启动应用程序时用户首先看到的页面,它也是顾客的第一印象和主要入口。就像精心布置的店铺橱窗一样,它需要展示最新的促销活动、季节性商品和热门商品。这里也是零售商展示最佳优惠、新品和热门商品的场所。主屏幕需要在促销内容和个人化推荐之间找到平衡,帮助客户快速找到他们感兴趣的内容,同时还可能发现他们感兴趣的其他新产品。
导航指南:店内布局就像实体店铺需要清晰的通道标识和合理的部门布局一样,数字导航对于帮助顾客找到方向至关重要。现代零售应用需要直观的导航模式,帮助用户在不同类别间切换,查看他们的购物车,并找到如店铺搜索或账户管理等重要功能。挑战在于如何组织数千种产品和功能,让顾客觉得自然且轻松。
数字购物助手:搜索和浏览搜索功能往往是那些知道自己想要什么的顾客最快捷的购物途径。现代零售应用程序需要具备能够理解顾客意图、处理拼写错误并提供相关搜索结果的强大搜索功能。这包括自动补全、最近搜索记录和可以根据价格、品牌或顾客评分等筛选结果等功能。另一方面,浏览功能则可以让顾客探索不同类别,发现他们可能未曾特意寻找的产品。
产品列表页:数字化货架无论通过什么方式到达,产品列表页面是顾客可以比较和选择相似商品的地方。这些页面需要在展示足够多的商品以供选择和提供足够信息以便明智选择之间找到平衡。智能筛选和排序选项可以帮助顾客根据具体需求缩小选项范围。
产品详情页面:产品检查这就是顾客做出购买决定的地方。就像在店里拿起并检查产品一样,产品详情页也需要提供顾客可能需要的所有信息,比如详尽介绍、高质量图片、价格、是否有货、规格和客户评价等。对于家庭装修产品,这可能还包括安装说明、所需工具和相关配件。
篮子:零售应用的核心所在在美国的术语中,购物篮(或购物车)不仅仅是商品的列表——它是一个管理整个购物过程的高级系统。它需要处理诸如查询库存、应用促销优惠、计算配送方式以及在不同设备上保持状态等场景。购物车需要在整个购物过程中易于访问而不显眼。
客户身份认证:客户身份管理系统现在的消费者期望一种购物体验,这种体验始于了解自己。身份系统需要在安全和便利之间找到平衡,提供访客结账等选项,方便偶尔购物的顾客,同时也为注册用户提供更多功能。该系统构成了个性化服务、订单记录和个人偏好的基础。
数字化结账:电子支付中心结账过程可以影响交易的成败。它需要既简便又安全,提供多种支付方式,同时确保交易安全。现代零售应用程序需要支持各种支付方式,从传统的信用卡到电子钱包,同时管理预约配送时段、地址验证及订单确认。
我的账号:客户中心“我的账户”部分是客户在应用中的个人空间。在这里,他们可以管理个人资料,查看订单历史,保存喜欢的物品,并维护配送偏好。这一部分需要提供对过去的购买记录、收藏的清单、配送追踪以及账户设置的轻松查看和访问。对于零售应用程序而言,这一部分对于维护长期客户关系并提供持续性和个性化的服务至关重要。
把所有的东西整合起来所有这些元素必须协同工作,以创造一个连贯的购物体验。零售应用的成功不仅仅在于是否具备这些功能,还取决于这些功能整合得如何以及它们是否能够很好地协同工作。就像一家实体商店需要考虑从进门到结账的整个客户旅程一样,数字零售体验也需要精心组织这些组件及其相关团队,以创造一个顺畅、直观的购物体验。
下面的图表展示了支持金丝雀PLC移动应用程序开发、维护以及功能提升的这些团队如下。
非功能性的考量除了面向客户的功能之外,一个现代的电子商务应用程序还需要一个强大的非功能性基础。安全在保护平台及其用户方面起着关键作用。机器人检测和反黑客措施确保自动威胁得到缓解,而不影响真实用户体验。API网关提供额外的安全保障,并有效地管理API流量,确保在高负载下保持稳定性能。
分析和监控对于保持应用的良好状态和性能非常重要。例如 Firebase Crashlytics 和 Dynatrace 这样的崩溃报告和性能监控工具,可以让团队在问题影响到用户前主动找出并解决这些问题。这些系统通过后台日志和实时警报来监控 API 性能并找出瓶颈。
运营卓越是由协调良好的支持团队来支持的,这些团队监督所有这些元素的顺利集成。开发人员、DevOps工程师和客户服务团队之间的合作确保了应用程序可靠运行并适应不断变化的要求。例如,事件管理流程能够快速解决这些问题,最大限度地减少停机时间。
最后,将这些非功能性需求整合到应用程序中需要严格的协调。部署管道、自动化测试和分阶段推出有助于交付这些功能而不影响用户体验。这些措施确保应用程序的安全性、高性能和可扩展性,不仅为客户提供可靠的基础,也为运营团队提供支持。
深度解读:一次技术革命浪潮(2016-2024)要想真正体会到移动开发的进步有多大,我们来看看实现一个产品列表页面——这对于任何电子商务应用来说都是一个基本功能。在2016年,这需要处理多个文件并处理复杂的视图重用逻辑。
iOS (2016年)这里是一个典型的商品列表页面(PLP),用Objective-C编写的可能的样子是这样的。
// ProductListViewController.h
@interface ProductListViewController : UIViewController <UITableViewDelegate, UITableViewDataSource>
@property (weak, nonatomic) IBOutlet UITableView *tableView;
@property (strong, nonatomic) NSArray<产品 *> *products;
@end
// ProductListViewController.m
@implementation ProductListViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self.tableView registerNib:[UINib nibWithNibName:@"产品单元" bundle:nil]
forCellReuseIdentifier:@"产品单元"];
[self 从网络获取产品];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return self.products.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath {
产品单元 *cell = [tableView dequeueReusableCellWithIdentifier:@"产品单元"];
产品 *product = self.products[indexPath.row];
[cell 配置产品:product];
return cell;
}
- (void)从网络获取产品 {
// 从网络获取产品...
}
@end
// ProductCell.h
@interface 产品单元 : UITableViewCell
@property (weak, nonatomic) IBOutlet UIImageView *productImageView;
@property (weak, nonatomic) IBOutlet UILabel *nameLabel;
@property (weak, nonatomic) IBOutlet UILabel *priceLabel;
- (void)配置产品:(产品 *)product;
@end
// ProductCell.m
@implementation 产品单元
- (void)配置产品:(产品 *)product {
self.nameLabel.text = product.name;
self.priceLabel.text = [NSString stringWithFormat:@"£%.2f", product.price];
// 使用第三方库加载图片...
}
@end
除此之外,用 Interface Builder 来设计一个包含数十行的 XML 和约束定义的界面也不罕见。
<!-- Main.storyboard -->
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="14460.31" targetRuntime="iOS.CocoaTouch">
<scenes>
<!--产品列表视图控制器-->
<scene sceneID="tne-QT-ifu">
<objects>
<viewController id="BYZ-38-t0r" customClass="ProductListViewController">
<view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<tableView clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="28" sectionFooterHeight="28" translatesAutoresizingMaskIntoConstraints="NO" id="Xft-lZ-gH7">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<connections>
<outlet property="dataSource" destination="BYZ-38-t0r" id="data-source-connection"/>
<outlet property="delegate" destination="BYZ-38-t0r" id="delegate-connection"/>
</connections>
</tableView>
</subviews>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstItem="Xft-lZ-gH7" firstAttribute="leading" secondItem="6Tk-OE-BBY" secondAttribute="leading" id="tableview-leading"/>
<constraint firstItem="6Tk-OE-BBY" firstAttribute="trailing" secondItem="Xft-lZ-gH7" secondAttribute="trailing" id="tableview-trailing"/>
<constraint firstItem="Xft-lZ-gH7" firstAttribute="top" secondItem="6Tk-OE-BBY" secondAttribute="top" id="tableview-top"/>
<constraint firstItem="6Tk-OE-BBY" firstAttribute="bottom" secondItem="Xft-lZ-gH7" secondAttribute="bottom" id="tableview-bottom"/>
</constraints>
<viewLayoutGuide key="safeArea" id="6Tk-OE-BBY"/>
</view>
<connections>
<outlet property="tableView" destination="Xft-lZ-gH7" id="tableview-outlet"/>
</connections>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
</objects>
</scene>
</scenes>
</document>
现在,我们来看看如何在SwiftUI中创建PLP,并了解一些强大的设计模式:
iOS (2024年) // 产品列表视图结构
struct 产品列表视图: 视图 {
@状态对象 private var 视图模型 = 产品列表视图模型()
var 体: 某些视图 {
列表(视图模型.产品列表) { 产品 in
产品行(产品: 产品)
}
.任务 {
等待 视图模型.加载产品()
}
}
}
// 产品行视图结构
struct 产品行: 视图 {
let 产品: 产品
var 体: 某些视图 {
水平堆叠 {
异步图像(url: 产品.图像URL)
.尺寸(宽度: 100, 高度: 100)
垂直堆叠(对齐方式: .leading) {
文本(产品.名称)
.字体(.标题)
文本(产品.价格.格式化(.货币(code: "GBP")))
.字体(.副标题)
}
}
}
}
这种对比非常明显。过去需要多个文件和超过200行代码,现在只需一个文件和30行简洁明了的代码即可搞定。
尽管这是一个相当通用的例子,但它强调了2024年创建应用程序时所使用语言的现代化。代码量大大减少,这减少了缺陷数量和执行和测试所需的时间。
这个SwiftUI实现引入了几个关键的设计模式,这些模式改变了我们构建用户界面的方法。
数据驱动视图:视图取决于其状态。当数据变化时,UI会自动刷新。
构成:复杂的UI是由更小的、可重用的视图构成的。每个视图都是一个自包含的单元,可以独立进行测试和预览。
声明式语法:我们只是描述我们想要的样子。SwiftUI会处理其余内容,包括高效的UI更新和流畅的动画。
除了代码精简之外,新的架构不仅显著提升了性能,还增强了可测试性。这些改进在移动应用的Android版本中也得到了同样的体现。
创建一个电子商务应用重新构建 B&Q 应用程序已经对开发流程和客户体验产生了显著影响。通过采用现代框架和架构模式,我们创建了一个可以适应客户的需求和市场的变化的系统。开发人员现在减少了处理重复任务的时间,更多的时间可以用来创造有意义的功能。这不仅提高了内部工作效率,还加快了新功能的推出速度。
对于客户来说,这种差异是显而易见的,这得益于内部设计团队抓住每一个机会来改进客户旅程。现在的应用程序提供了更顺畅、更快捷和更可靠的购物体验。无论你是搜索产品、查看库存还是结账,整个过程都变得更流畅自然了。
这些改进不仅局限于应用本身。通过与后端前端系统(如GraphQL)的集成,并利用Kotlin多平台技术,我们构建了一个支持跨平台一致行为的基础,并且能够适应未来创新。因此,这款应用不仅仅是为了今天的工具,而是能够应对未来零售挑战的可扩展解决方案。
这不仅仅是一个单纯的技术项目;这是我们在手机应用开发上的文化转变。
2024年7月 — 提交至应用商店2024年夏天,我们发布了新的B&Q应用程序到苹果和谷歌的应用商店。与TradePoint是全新的应用不同,B&Q应用程序以更新的形式出现在现有的应用程序之上。细心的用户可能会注意到,版本号从之前的9.x.x升级到了10.00版本。
我们将新的数字产品交给客户也伴随着一定的风险。我们采取了分阶段推出的方式,并制定了可撤回的计划,以减轻这些风险。
当有新版本可用时,我们并不喜欢总是立即强制客户下载并安装新版本。这导致在几周的时间里,我们需要同时支持旧版和新版的应用程序。当90%的用户开始使用新版本时,我们对那些仍在使用旧版本的用户进行了强制更新,从而有效地淘汰了之前的B&Q移动应用程序版本。
前方之路零售格局将继续变化,但凭借我们现代且灵活的架构,我们准备随它一起演变。
B&Q已经进入了类似亚马逊模式的第三方卖家市场,也就是销售第三方卖家的产品,现在应用程序中有数以百万计的商品可供购买。
B&Q与其它电商平台如Temu相比,一个主要优势在于其实体店的存在,这让顾客可以当天取货,只要商品有库存的话。这样一来,购买并马上拿到手的产品只需要在手机上点几下,然后快速去附近门店拿货就可以了。
由翠鸟有限公司工程核心驱动的B&Q应用程序现在有了一个坚实的基础来发展和进一步完善。电子商务应用程序的基本框架和功能已经建立起来,这花了大约18个月的时间来实现。旅程并未就此结束。通过不断迭代,我们将进一步在应用中加入更多的购买细节。事实证明,通过移动应用购买的用户往往更忠诚,转化率也更高。接下来的18个月,我们将继续巩固并提升用户体验。预期的收入增长将使B&Q应用程序在未来几年中处于市场优势地位。可以说,技术不会成为未来18个月发展的瓶颈,这令人满意。
在构思、启动、构建和交付 B&Q 应用程序的过程中,这让我们在开发 TradePoint 应用程序时有了显著的优势。通过利用已有的代码、技术方案和人员,我们仅用了三个多月就完成了 TradePoint 应用程序的交付,并在 2024 年 9 月发布到各大应用商店。
感谢你的坚持看到最后,2025年还会有很多文章,敬请期待。如需更多见解,可直接通过我的LinkedIn(在这里)联系我。
如果您希望成为Kingfisher PLC工程未来的一员,请看看我们的数字职位机会,了解更多。
B&Q 可在各大应用商店找到共同学习,写下你的评论
评论加载中...
作者其他优质文章