Station创始人Django:SaaS启动的更好的软件架构

2021-06-24 01:44:16

我们' D通过一些潜在的客户进行想法,建立产品,然后立即乘坐甜蜜的指数增长曲线,提前退休。 在现实生活中,甚至才能成为十亿美元公司的初创公司通常通过阶段进行: 花费几个月的建筑功能,潜在的客户说他们' d绝对支付,只有让他们没有转换。 最后到达产品市场合适,只能雇用太快并创造内部混乱。 丢失关键客户或耗尽资金,并不得不摆脱大部分球队。 聘请初级开发人员致力于核心产品,通常在所使用的技术上很少或没有经验。 显然,在没有大量的东西的情况下,营造出一个巨大的结果,但即使在疯狂地成功的初创公司中,日常经验很少迷人。

在过去的几十年中,我们看到了大多数公司切换到使用敏捷和其他项目管理方法,不仅考虑了这些转移背景,而且完全由它们设计。我们没有看到什么,至少没有到几乎相同的程度,是相同的思想程度,以建立软件本身。与此同时,敏捷是在假设产品团队和第39位的优先级和其开发速度方面设计的,而且可能会发生疯狂地改变,同样应该选择创建软件时使用的设计模式确保这件事:

这些业务不仅可以保持灯光,而且即使它达到最后一次(初级)开发人员,也可以继续前进。

我撰写了本指南,解释了如何以最大化您的启动必须成功的机会数量的方式编写软件 - 通过使开发速度易于维护,无论团队规模,开发人员能力和开发者的能力如何变化经验,产品功能等。这一想法是,鉴于固有的不确定性,初创性可以通过将一些基本系统施加到最大限度地提高他们可以测试的想法,功能和假设的数量来大规模增加成功的几率;换句话说,最大化"铅笔,"借用本博客文章由Ben Horowitz借阅。以下是建立REST API的一系列模式和最佳实践,特别适合SaaS启动和消费者应用的需求。 [1]

它非常重要,了解每个技术推荐如何将真实世界的利益赋予您的业务,以及最好的方法是在所谓的行动的背景下讨论它们。在软件架构中,幂量是我们用来描述代码库本身的特征的单词,与最终用户的代码实际上是什么。关于软件架构的大多数现有建议是为10B +公司编写的,因此倾向于专注于最大化性能,可扩展性,可用性,可靠性等的事物[2]

这实际上创造了一个巨大的问题。问题是,由于在大学学习了CS,大多数工程师在初创公司工作没有大量的商业培训。因此,经常发生的是,这些开发人员决定如何通过仅在谷歌或堆栈溢出中复制第一个推荐的建议来决定如何实现特征,而没有首先评估商业角度的建议,而且由于大多数此建议都朝向财富500强的公司进行了准备(在一个极端)或Hackathon型爱好项目(另一个),这不可避免地导致巨大的看不见的成本。那么开发人员和经理应该如何做出决策?基本上,始终如一地为任何特定业务做出良好的建筑决策的方式,就是了解哪些令人征权限会产生价值,确保团队了解它们为什么自己创造价值,然后具有系统,以确保代码以与对齐的方式编写这种理解。

谈到SaaS启动和消费者应用程序,特别是那些拥有一些用户和牵引但在ARR中少于100米的人,关键洞察力是API电源几乎所有这些业务都具有相同的基本特征:

在任何特定时间内由8个或更少的开发人员维护,或者如果他们是良好的批评

大多数复杂性都在业务逻辑中,而不是在高级CS概念中

因为大多数初创公司都有类似的代码库和面对类似的挑战,它通常是相同的行动量,最大化每个启动和#39; S的成功可能性。这是哪种司对初创公司最有价值?在我的经验中,答案是:可预测性,可读性,简单性和升级性。对于每个IS,我' ll首先解释它如何在高级别创建值,然后给出一些可以应用于您自己的代码库的一些特定模式和规则。最后,我解释了为什么我认为这些模式和规则是统称的现代Web应用程序开发团队的Mise。

本指南中的代码片段是使用Python,Django和DRF编写的,而是他们'即使您不知道' the moying matth或django,也可以旨在可以理解。我创建了一个完整的应用程序,使用这些代码片段,以确保他们在此发布的工作。选择Python的原因是它通常是初创公司的最佳语言,在arr中少于100米;作为一切的所谓的第二个最佳语言,Python为尝试使用不同的产品功能和货币化模型来启动最初始化。它和#39也很容易雇用。我确实讨论了特定于Django的一些问题,但大多数建议是适用的,无论您如何使用什么语言和网络框架;

每个人都知道创建软件恰到昂贵,但几乎每个人都大大低估了其总体拥有成本。

根据各种学术和行业估计,60% - 与任何特定的代码相关的成本的60% - 80%是在该系列原稿写之后维护的。这是由于错误,更改功能要求,更新到依赖关系,需要更换或维护的依赖性,未替换的依赖关系等等,应该容易理解为什么最好类型的代码通常是永不的代码首先写完。

对于上下文,重要的是要明白,在大多数CodeBases中,大部分维护成本aren' t不可避免,而是因为现有的代码是以这样的方式编写的,这使得它耗时令读和理解。当CodeBase新的开发人员任务时,甚至是一个基本的变化,它通常需要几天或几周的研究,然后在第一行书写或编辑之前。即使是有能力和经验丰富的开发人员而言,这是真实的,所以它'因为这个原因,下一个最佳类型的代码是' s完全可预测的代码。

从本质上讲,每个REST端点都应该以相同的顺序执行相同的基本步骤。这样如果一个人有足够的知识来了解任何一个终点的知识,他们应该有足够的知识来了解每个端点的工作原理。如果它们'重新任务修复错误或添加功能,应该有一个简单的逐步处理,它们每次都可以使用,以确切地查找它们需要修改或扩展代码的代码库中的位置。

考虑每个报纸文章如何遵循相同的基本反相金字塔格式,并回答相同的基本问题,对谁,什么,何时,为什么以及如何。本次公约并不是任何方式限制哪些主题记者可以写出或对他们的想法的复杂程度限制;它只是让报纸文章超级易于阅读,即使是在中学阅读水平的人们甚至可以轻松地记住与朋友分享的主要想法很容易。

这种清晰度和不知情不仅仅是我们可以的东西 - 而且应该 - 渴望在我们的代码中。相反,它'我们实际上可以获得85%的途径,以通过使我们的每个终点遵循相同的可预测模式。

关键识别是,任何给定的REST端点都将执行多达七个基本步骤。对于许多端点,有多个不同的订单可以执行这些步骤,但也恰好存在始终为每个端点工作的特定顺序。事实证明,始终执行这七个步骤,或者至少以相同的方式执行这七个步骤,或者至少是任何给定的端点所必需的子集,并且以相同的顺序

将输入复制到本地变量 - 此端点采取哪些参数(查询参数或身体参数)?

验证用户输入 - 确保用户以正确的格式为此端点提供所有相同的所需参数。如果存在错误,请将所有输入验证误差汇入如下所述的字典样式响应。

强制执行业务需求 - 检查允许用户访问端点的情况,并正确提交所有必需参数,但它们允许由于应用程序的业务逻辑而执行特定操作。例如。使用已拍摄的用户名创建帐户。如果出现错误,请返回如下所述发生的第一个错误。

执行业务逻辑 - 无论这个端点实际上都应该做,例如,做任何事情,例如,更改数据库中的状态,将数据返回到API消费者,将数据发送到第三方处理器等。

返回HTTP响应 - 返回API消费者所需的任何数据以及状态代码。

这可能会袭击一些过于规定的和公式化。但是,没有人想回到英雄的时代,拥抱副教机的方式,更加平淡的风格具有许多好处 - 大大降低了代码库的总体拥有成本,并增加了开发者的速度比任何东西增加否则你可能会实施。如何?非常简单,它'因为:

如果没有首先被审计以获得正确和安全性,则新代码应该永远不会直播。

它'速度快得多,以确定是否正确完成了事情,如果它们总是以相同的顺序发生。例如,如果您发现用户输入Hasn' t在视图的开头进行消毒,您需要阅读' t需要通过实现业务逻辑的所有方法来查看它是否'稍后完成 - 您已经知道它' s已不正确完成,并且存在安全问题。

在未来,修复错误,更改业务逻辑,以及执行维护,都需要了解代码中的更改中的位置。如果每个端点的实现遵循相同的模式,则无需浪费数小时或数日通过代码返回,即与您需要完成的内容相关。

与上面的报纸示例一样,确保每个端点符合此模式,每次读者都需要揭示五个WS以及该端点的情况下,每次都会大大减少所需的时间(和成本)。为了做出任何必要的变化。 Let'查看用于创建新用户帐户的示例代码片段,使用Django和Django REST框架编写,然后逐步浏览IT: 类用户(APIView): permission_classes =(accountCreation,) # 创建帐号 def post(自我,请求): UNSEAFE_USERNMAME = REQUEST.DATA.GET("用户名""") UNSEASE_EMAIL_ADDRESS = REQUEST.DATA.GET("电子邮件_Address""") UNSEASE_TERMS_OF_SERVICE_ACCEPTED = REQUEST.DATA.GET(" ristm_of_service_accepted" none) UNSEAFE_PASSWORD = REQUEST.DATA.GET("密码""") sanitized_username = sanitization_utils.strip_xs(不安全_username) sanitized_email_address = sanitization_utils.strip_xs(不安全_email_address) sanitized_terms_of_service_accepted = sanitization_utils.string_to_boolean(不安全_terms_of_service_accepted) 尝试: user_model,auth_token = contain_management_service.create_Account( 要求, sanitized_username, sanitized_email_address, 不安全, sanitized_terms_of_service_accepted, ) 除MarshmAllow .Exceptions.ValidationError作为E: 返回get_validation_error_response(validation_error = e,http_status_code = 422) 除custom_errors.usernamealreadyexistroris外,e: 返回get_business_requirement_error_response(business_logic_error = e,http_status_code = 409) 除custom_errors.emailAddressalReadyExisterRor之外: 返回get_business_requirement_error_response(business_logic_error = e,http_status_code = 409) 除custom_errors.termsnotacceptederror除外: 返回get_business_requirement_error_response(business_logic_error = e,http_status_code = 429) resp = {"数据&#34 ;: {" auth_token&#34 ;: auth_token}} 返回响应(数据= RESP,Status = 201)

那么这里究竟发生了什么呢?让' s逐行浏览此行,并讨论该代码片段的内容。

第2行:指定谁可以访问视图中的每个端点。这里重要的是,每个端点的权限在每个视图的顶部明确指定,而不是隐式继承从您的Django设置文件。这使得很容易理解发生的事情,审核CodeBase进行安全问题,并在将来必要进行任何变化。

第6-9行:将每个输入参数复制到新变量中。这使得包括前端开发人员和产品经理在内的任何人都可以轻松弄清楚每个端点预期的输入 - 即使在写入任何文档之前。我们使用不安全_前缀每个变量,引起注意他们没有尚未消毒的事实'

第11-13行:始终消毒用户输入的安全漏洞。 [4]此规则的一个常见异常是密码;它们'重新存储为哈希,所以没有xss向量,并且意外从密码中剥离复杂性是它自己的安全风险。但除了这一例外,在使用IT验证,业务逻辑,存储,演示文稿等中的其他任何内容之前,应始终消毒用户输入。在规则#4中解释了这一点的任何其他原因。

第16-22行:终点实际上是什么;在这种情况下,创建用户帐户。如果我们想了解实际创建帐户的实现细节,这是我们需要挖掘的方法。一个快点要注意的是,我们'重新调用account_management_service.conort(...),而不是从account_management_service导入service方法create_account并直接调用它。这让读者立即知道它们' d需要打开哪个文件,以查看方法' s实现,而不滚动到当前文件的顶部或使用IDE。虽然当从文件调用函数时,文件名用作代码库的其余部分的广告。获取每个函数生活的常量提醒使得开发人员将更有可能在正确的位置添加新代码,而不是将其放在错误的地方,或者更糟糕的是已经存在的代码。

第23 - 30行:端点可以返回的所有可能的错误。这些在规则#10下更详细地讨论。

以这种方式编写观点的原因是它使每个端点对五个WS回答五个WS。也就是说,我们可以立即看到谁可以访问终点,输入端点接受的是什么,如何消除该输入,其中执行业务逻辑,当事情侧向时,终点的原因是什么?一个成功的回复看起来像。

让'将此对比这是一个实例,它基本上直接从Django Rest Framework教程中直接:

类用户(APIView): def post(自我,请求): Serializer = Userserializer(Data = Request.Data) 如果serializer.is_valid(): serializer.save() 返回响应(Serializer.data,status = status.http_201_created) 返回响应(Serializer.Errors,status = status.http_400_bad_request)

在那里没有办法通过查看这个被允许访问端点的方式,输入端点接受的是什么,无论是正确的消毒,它返回哪些数据,可能错误等等。和它&# 39;甚至没有看待用户统一化器的代码来解决这个问题;对于任何现实世界的应用,这些问题的答案可能会很大埋入多个层面,并且获得足够的答案可能不仅仅是几个小时而是几个月。

叫我老式,但我相信: 代码应该是' s的逐步指令集,如食谱一样读取

你应该能够通过阅读它来了解哪些代码,或者至少有关于发生什么&#39的直接。

在这里,您'实际上使用奇怪的DSL来通过设置类属性和覆盖方法来指定行为。这对像Django管理员这样的东西有意义,你在哪里'重新配置GUI。但是对于实际应用程序开发,GenericViews与哪个好的代码应该相反。

例如,可以有一些合法用例编写这样的书写代码,例如:对于Hackathon,或客户的演示,或者在公开部署的任何其他快速原型。问题是,很多开发人员似乎采取了这个工具作为一种隐含的建议,他们应该以这种方式编写生产代码。然后,所有教程和堆栈溢出答案都是由可能使用其Web框架的人贡献的所有教程和堆栈溢出答案复合,他们仅作为有趣的技术玩具,或者至少以与任何类型的业务环境基于任何基础的东西不同的方式。基本上是GenericViews最好避免,总的来说,我认为Django社区将从核心Django中迁移到一个康乐包中。

您可以在Django中将业务逻辑放在其中有三个不同的地方:在模型或模型管理器中,表单或序列化程序以及服务中。我认为业务逻辑应该只能进入服务的意见。我' ll解释为什么,但首先让' s解释一下服务,然后通过一个例子。

A'服务'只是一个具有一堆函数的文件,包含与应用程序的某些部分相关的所有业务逻辑。例如。对于FWD:每个人,我们拥有的一些服务是account_management_service.py,thread_upload_service.py,content_discovery_service.py等。每个服务文件通常与视图文件密切对应,例如, account_management_views.py或thread_upload_views.py。服务在视图和模型之间生活,以便每个端点的请求响应生命周期如下所示:

视图将任何查询参数或正文数据复制到本地变量中,消除这些本地变量以获取安全问题,然后将Smisitized变量传递给服务。

该服务验证用户输入(例如,使用Serializers),强制执行业务需求,执行所有业务逻辑,然后返回对视图的任何输出或错误。

作为一个例子,Let' s查看account_management_service.create_account的一个可能的实现,从上一节中的服务方法:

def create_account(请求,sanitized_username,sanitized_email_address,Unsafe_password, sanitized_terms_of_service_accepted): fields_to_validate_dict = { "用户名&#34 ;: sanitized_username, "电子邮件_address&#34 ;: sanitized_email_address, " password":不安全_password, " sermar_of_service&#34 ;: sanitized_terms_of_service_accepted, }

......