การเลือกทางเลือก Tech Stack - The Ups and Downs

เผยแพร่แล้ว: 2022-03-11

หากเว็บแอปพลิเคชันมีขนาดใหญ่และเก่าเพียงพอ อาจมีบางครั้งที่คุณต้องแยกย่อยเป็นส่วนย่อยๆ แยกออกมาและแยกบริการออกจากมัน ซึ่งบางส่วนจะมีความเป็นอิสระมากกว่าส่วนอื่นๆ สาเหตุบางประการที่อาจกระตุ้นให้เกิดการตัดสินใจดังกล่าว ได้แก่ การลดเวลาในการเรียกใช้การทดสอบ ความสามารถในการปรับใช้ส่วนต่างๆ ของแอปอย่างอิสระ หรือการบังคับใช้ขอบเขตระหว่างระบบย่อย การแยกบริการต้องใช้วิศวกรซอฟต์แวร์ในการตัดสินใจที่สำคัญหลายๆ อย่าง และหนึ่งในนั้นคือสิ่งที่กลุ่มเทคโนโลยีที่จะใช้สำหรับบริการใหม่

ในโพสต์นี้ เราแบ่งปันเรื่องราวเกี่ยวกับการดึงบริการใหม่ออกจากแอปพลิเคชันแบบเสาหิน – แพลตฟอร์ม Toptal เราอธิบายว่าเราเลือกสแตกทางเทคนิคใดและเพราะเหตุใด และสรุปปัญหาเล็กน้อยที่เราพบระหว่างการใช้งานบริการ

บริการ Chronicles ของ Toptal เป็นแอพที่จัดการการกระทำของผู้ใช้ทั้งหมดที่ดำเนินการบนแพลตฟอร์ม Toptal การดำเนินการเป็นรายการบันทึกเป็นหลัก เมื่อผู้ใช้ทำบางสิ่ง (เช่น เผยแพร่บล็อกโพสต์ อนุมัติงาน ฯลฯ) รายการบันทึกใหม่จะถูกสร้างขึ้น

แม้ว่าจะดึงมาจากแพลตฟอร์มของเรา แต่โดยพื้นฐานแล้วมันไม่ได้ขึ้นอยู่กับมันและสามารถใช้กับแอพอื่น ๆ ได้ นี่คือเหตุผลที่เราเผยแพร่บัญชีโดยละเอียดของกระบวนการและหารือเกี่ยวกับความท้าทายจำนวนหนึ่งที่ทีมวิศวกรของเราต้องเอาชนะขณะเปลี่ยนไปใช้สแต็กใหม่

มีเหตุผลหลายประการที่อยู่เบื้องหลังการตัดสินใจแยกบริการและปรับปรุงสแตก:

  • เราต้องการให้บริการอื่นๆ สามารถบันทึกเหตุการณ์ที่สามารถแสดงและใช้ที่อื่นได้
  • ขนาดของตารางฐานข้อมูลที่เก็บบันทึกประวัติเพิ่มขึ้นอย่างรวดเร็วและไม่เป็นเชิงเส้น ทำให้มีต้นทุนการดำเนินงานสูง
  • เราถือว่าการใช้งานที่มีอยู่เป็นภาระหนี้ทางเทคนิค

ตารางการดำเนินการ - ตารางฐานข้อมูล

เมื่อมองแวบแรก ดูเหมือนเป็นการริเริ่มที่ตรงไปตรงมา อย่างไรก็ตาม การรับมือกับเทคโนโลยีทางเลือกมีแนวโน้มที่จะสร้างข้อเสียที่ไม่คาดคิด และนั่นคือสิ่งที่บทความในวันนี้มุ่งเป้าไปที่การแก้ไข

ภาพรวมสถาปัตยกรรม

แอปพลิเค Chronicles ประกอบด้วยสามส่วนที่สามารถเป็นอิสระไม่มากก็น้อยและทำงานในคอนเทนเนอร์ Docker ที่แยกจากกัน

  • ผู้บริโภค Kafka เป็นผู้บริโภค Kafka ที่ใช้ Karafka ที่มีข้อความสร้างรายการน้อยมาก มันจัดคิวข้อความที่ได้รับทั้งหมดไปยัง Sidekiq
  • ผู้ปฏิบัติงาน Sidekiq เป็นผู้ปฏิบัติงานที่ประมวลผลข้อความ Kafka และสร้างรายการในตารางฐานข้อมูล
  • จุดปลาย GraphQL:
    • ตำแหน่งข้อมูล สาธารณะ เปิดเผย API การค้นหารายการ ซึ่งใช้สำหรับฟังก์ชันต่างๆ ของแพลตฟอร์ม (เช่น เพื่อแสดงคำแนะนำเครื่องมือความคิดเห็นบนปุ่มการคัดกรอง หรือแสดงประวัติการเปลี่ยนแปลงงาน)
    • ปลายทางภายใน ให้ความสามารถในการสร้างกฎแท็กและเทมเพลตจากการย้ายข้อมูล

พงศาวดารใช้เพื่อเชื่อมต่อกับสองฐานข้อมูลที่แตกต่างกัน:

  • ฐานข้อมูลของตัวเอง (ที่เราเก็บกฎแท็กและแม่แบบ)
  • ฐานข้อมูลแพลตฟอร์ม (ที่เราจัดเก็บการกระทำที่ผู้ใช้ดำเนินการ รวมทั้งแท็กและการติดแท็ก)

ในกระบวนการแยกแอป เราได้ย้ายข้อมูลจากฐานข้อมูลแพลตฟอร์มและปิดการเชื่อมต่อแพลตฟอร์ม

แผนเบื้องต้น

เริ่มแรก เราตัดสินใจเลือกใช้ Hanami และระบบนิเวศทั้งหมดที่มีให้โดยค่าเริ่มต้น (โมเดลฮานามิสนับสนุนโดย ROM.rb, dry-rb, hanami-newrelic ฯลฯ) การปฏิบัติตามวิธี "มาตรฐาน" ในการทำสิ่งต่าง ๆ สัญญาว่าเรามีแรงเสียดทานต่ำ ความเร็วในการใช้งานที่ยอดเยี่ยม และ "ความสามารถในการใช้งาน Google" ที่ดีมากสำหรับปัญหาใดๆ ที่เราอาจเผชิญ นอกจากนี้ ระบบนิเวศของฮานามิยังเติบโตเต็มที่และเป็นที่นิยม และห้องสมุดก็ได้รับการดูแลอย่างดีจากสมาชิกที่เคารพนับถือของชุมชนทับทิม

นอกจากนี้ ส่วนใหญ่ของระบบได้ถูกนำไปใช้ในด้านแพลตฟอร์มแล้ว (เช่น จุดสิ้นสุดการค้นหารายการของ GraphQL และการดำเนินการ CreateEntry) ดังนั้นเราจึงวางแผนที่จะคัดลอกโค้ดจำนวนมากจากแพลตฟอร์มไปยัง Chronicles ตามที่เป็นอยู่ โดยไม่ทำการเปลี่ยนแปลงใดๆ นี่ก็เป็นหนึ่งในเหตุผลหลักที่เราไม่ได้ไปกับ Elixir เนื่องจาก Elixir ไม่อนุญาต

เราตัดสินใจที่จะไม่ทำ Rails เพราะรู้สึกว่าเกินความสามารถสำหรับโครงการเล็กๆ เช่นนี้ โดยเฉพาะอย่างยิ่งสิ่งต่างๆ เช่น ActiveSupport ซึ่งไม่ได้ให้ประโยชน์ที่เป็นรูปธรรมมากมายสำหรับความต้องการของเรา

เมื่อแผนไปทางใต้

แม้ว่าเราจะพยายามอย่างเต็มที่เพื่อยึดตามแผน แต่ในไม่ช้าแผนนี้ก็พังทลายลงด้วยเหตุผลหลายประการ หนึ่งคือเราขาดประสบการณ์กับสแต็กที่เลือก ตามด้วยปัญหาจริงของสแต็กเอง จากนั้นมีการตั้งค่าที่ไม่ได้มาตรฐานของเรา (ฐานข้อมูลสองฐานข้อมูล) ในท้ายที่สุด เราตัดสินใจที่จะกำจัด hanami-model จากนั้น Hanami เองแทนที่ด้วย Sinatra

เราเลือกซินาตราเพราะเป็นห้องสมุดที่ได้รับการดูแลอย่างดีซึ่งสร้างขึ้นเมื่อ 12 ปีที่แล้ว และเนื่องจากเป็นห้องสมุดที่ได้รับความนิยมมากที่สุดแห่งหนึ่ง ทุกคนในทีมจึงมีประสบการณ์ตรงมาก

การพึ่งพาที่เข้ากันไม่ได้

การสกัด Chronicles เริ่มต้นในเดือนมิถุนายน 2019 และก่อนหน้านั้น Hanami เข้ากันไม่ได้กับเวอร์ชันล่าสุดของ dry-rb gem กล่าวคือ Hanami เวอร์ชันล่าสุดในขณะนั้น (1.3.1) รองรับเฉพาะการตรวจสอบความถูกต้องแบบแห้ง 0.12 และเราต้องการการตรวจสอบแบบแห้ง 1.0.0 เราวางแผนที่จะใช้สัญญาจากการตรวจสอบแบบแห้งที่เปิดตัวใน 1.0.0 เท่านั้น

นอกจากนี้ Kafka 1.2 ยังเข้ากันไม่ได้กับ dry gem ดังนั้นเราจึงใช้เวอร์ชันที่เก็บของมัน ปัจจุบันเราใช้ 1.3.0.rc1 ซึ่งขึ้นอยู่กับ dry gem ตัวใหม่ล่าสุด

การพึ่งพาที่ไม่จำเป็น

นอกจากนี้ Hanami gem ยังรวมการพึ่งพาที่เราไม่ได้วางแผนที่จะใช้มากเกินไป เช่น hanami-cli , hanami-assets , hanami-mailer , hanami-view และแม้แต่ hanami-controller นอกจากนี้ เมื่อพิจารณาจาก readme รุ่นฮานามิ จะเห็นได้ชัดเจนว่าสนับสนุนฐานข้อมูลเพียงฐานข้อมูลเดียวโดยค่าเริ่มต้น ในทางกลับกัน ROM.rb ซึ่ง hanami-model รองรับการกำหนดค่าหลายฐานข้อมูลตั้งแต่แกะกล่อง

โดยรวมแล้ว ฮานามิโดยทั่วไปและโดยเฉพาะ hanami-model ดูเหมือนเป็นนามธรรมในระดับที่ไม่จำเป็น

ดังนั้น 10 วันหลังจากที่เราสร้าง PR ที่มีความหมายครั้งแรกให้กับ Chronicles เราจึงแทนที่ Hanami ด้วย Sinatra โดยสิ้นเชิง เราสามารถใช้ Rack ล้วนๆ ได้เช่นกัน เพราะเราไม่ต้องการการกำหนดเส้นทางที่ซับซ้อน (เรามีจุดปลาย "คงที่" สี่จุด - จุดปลาย GraphQL สองจุด จุดปลาย /ping และเว็บอินเตอร์เฟส sidekiq) แต่เราตัดสินใจที่จะไม่ฮาร์ดคอร์เกินไป ซินาตราเหมาะกับเราเป็นอย่างดี หากคุณต้องการเรียนรู้เพิ่มเติม โปรดดูบทแนะนำเกี่ยวกับซินาตราและภาคต่อของเรา

ความเข้าใจผิดเกี่ยวกับ Dry-schema และ Dry-validation

เราต้องใช้เวลาพอสมควรและการลองผิดลองถูกหลายครั้งเพื่อค้นหาวิธี "ปรุง" การตรวจสอบความถูกต้องแบบแห้งอย่างถูกต้อง

 params do required(:url).filled(:string) end params do required(:url).value(:string) end params do optional(:url).value(:string?) end params do optional(:url).filled(Types::String) end params do optional(:url).filled(Types::Coercible::String) end

ในตัวอย่างด้านบน พารามิเตอร์ url ถูกกำหนดด้วยวิธีที่แตกต่างกันเล็กน้อยหลายวิธี คำจำกัดความบางอย่างเทียบเท่ากัน และคำจำกัดความอื่นๆ ก็ไม่สมเหตุสมผล ในตอนแรก เราไม่สามารถแยกแยะความแตกต่างระหว่างคำจำกัดความเหล่านั้นได้ เนื่องจากเราไม่เข้าใจอย่างถ่องแท้ ด้วยเหตุนี้ สัญญาเวอร์ชันแรกของเราจึงค่อนข้างยุ่งเหยิง เมื่อเวลาผ่านไป เราได้เรียนรู้วิธีการอ่านและเขียนสัญญา DRY อย่างถูกต้อง และตอนนี้สัญญาเหล่านี้ดูสอดคล้องและสง่างาม อันที่จริง ไม่เพียงแต่สง่างามเท่านั้น แต่ยังสวยงามอีกด้วย เรายังตรวจสอบการกำหนดค่าแอปพลิเคชันด้วยสัญญา

ปัญหาเกี่ยวกับ ROM.rb และ Sequel

ROM.rb และ Sequel ต่างจาก ActiveRecord ไม่น่าแปลกใจเลย แนวคิดเริ่มต้นของเราที่ว่าเราจะสามารถคัดลอกและวางโค้ดส่วนใหญ่จากแพลตฟอร์มล้มเหลว ปัญหาคือส่วนแพลตฟอร์มนั้นเน้น AR มาก ดังนั้นเกือบทุกอย่างต้องเขียนใหม่ใน ROM/Sequel เราจัดการเพื่อคัดลอกโค้ดเพียงส่วนเล็กๆ ซึ่งไม่ขึ้นกับเฟรมเวิร์ก ระหว่างทาง เราประสบปัญหาที่น่าผิดหวังและข้อบกพร่องบางประการ

กรองตามข้อความค้นหาย่อย

ตัวอย่างเช่น ฉันใช้เวลาหลายชั่วโมงในการค้นหาวิธีสร้างแบบสอบถามย่อยใน ROM.rb/Sequel นี่คือสิ่งที่ฉันจะเขียนโดยไม่ต้องตื่นใน Rails: scope.where(sequence_code: subquery ) ในภาคต่อ กลับกลายเป็นว่าไม่ง่ายอย่างนั้น

 def apply_subquery_filter(base_query, params) subquery = as_subquery(build_subquery(params)) base_query.where { Sequel.lit('sequence_code IN ?', subquery) } end # This is a fixed version of https://github.com/rom-rb/rom-sql/blob/6fa344d7022b5cc9ad8e0d026448a32ca5b37f12/lib/rom/sql/relation/reading.rb#L998 # The original version has `unorder` on the subquery. # The fix was merged: https://github.com/rom-rb/rom-sql/pull/342. def as_subquery(relation) attr = relation.schema.to_a[0] subquery = relation.schema.project(attr).call(relation).dataset ROM::SQL::Attribute[attr.type].meta(sql_expr: subquery) end

ดังนั้นแทนที่จะใช้บรรทัดเดียวอย่างง่าย ๆ เช่น base_query.where(sequence_code: bild_subquery(params)) เราต้องมีหลายสิบบรรทัดที่มีโค้ดที่ไม่สำคัญ แฟรกเมนต์ SQL แบบดิบ และความคิดเห็นแบบหลายบรรทัดที่อธิบายว่าเหตุใดกรณีที่โชคร้ายนี้ บวม

สมาคมที่มีฟิลด์เข้าร่วมที่ไม่สำคัญ

ความสัมพันธ์ของ entry ( ตาราง performed_actions _actions) มีฟิลด์ id หลัก อย่างไรก็ตาม ในการเข้าร่วมกับตาราง *taggings จะใช้คอลัมน์ sequence_code ใน ActiveRecord จะแสดงค่อนข้างง่าย:

 class PerformedAction < ApplicationRecord has_many :feed_taggings, class_name: 'PerformedActionFeedTagging', foreign_key: 'performed_action_sequence_code', primary_key: 'sequence_code', end class PerformedActionFeedTagging < ApplicationRecord db_belongs_to :performed_action, foreign_key: 'performed_action_sequence_code', primary_key: 'sequence_code' end

สามารถเขียนแบบเดียวกันใน ROM ได้เช่นกัน

 module Chronicles::Persistence::Relations::Entries < ROM::Relation[:sql] struct_namespace Chronicles::Entities auto_struct true schema(:performed_actions, as: :entries) do attribute :id, ROM::Types::Integer attribute :sequence_code, ::Types::UUID primary_key :id associations do has_many :access_taggings, foreign_key: :performed_action_sequence_code, primary_key: :sequence_code end end end module Chronicles::Persistence::Relations::AccessTaggings < ROM::Relation[:sql] struct_namespace Chronicles::Entities auto_struct true schema(:performed_action_access_taggings, as: :access_taggings, infer: false) do attribute :performed_action_sequence_code, ::Types::UUID associations do belongs_to :entry, foreign_key: :performed_action_sequence_code, primary_key: :sequence_code, null: false end end end

มีปัญหาเล็กน้อยกับมันแม้ว่า มันจะคอมไพล์ได้ดี แต่ล้มเหลวในรันไทม์เมื่อคุณพยายามใช้งานจริง

 [4] pry(main)> Chronicles::Persistence.relations[:platform][:entries].join(:access_taggings).limit(1).to_a E, [2019-09-05T15:54:16.706292 #20153] ERROR -- : PG::UndefinedFunction: ERROR: operator does not exist: integer = uuid LINE 1: ...ion_access_taggings" ON ("performed_actions"."id" = "perform... ^ HINT: No operator matches the given name and argument types. You might need to add explicit type casts.: SELECT <..snip..> FROM "performed_actions" INNER JOIN "performed_action_access_taggings" ON ("performed_actions"."id" = "performed_action_access_taggings"."performed_action_sequence_code") ORDER BY "performed_actions"."id" LIMIT 1 Sequel::DatabaseError: PG::UndefinedFunction: ERROR: operator does not exist: integer = uuid LINE 1: ...ion_access_taggings" ON ("performed_actions"."id" = "perform...

เราโชคดีที่ประเภทของ id และ sequence_code ต่างกัน ดังนั้น PG จึงแสดงข้อผิดพลาดประเภท หากเป็นประเภทเดียวกันใครจะรู้ว่าฉันจะใช้เวลาแก้ไขข้อบกพร่องนี้กี่ชั่วโมง

ดังนั้น entries.join(:access_taggings) จึงไม่ทำงาน จะเกิดอะไรขึ้นหากเราระบุเงื่อนไขการเข้าร่วมอย่างชัดเจน? เช่นเดียวกับรายการ. entries.join(:access_taggings, performed_action_sequence_code: :sequence_code) ตามที่เอกสารอย่างเป็นทางการแนะนำ

 [8] pry(main)> Chronicles::Persistence.relations[:platform][:entries].join(:access_taggings, performed_action_sequence_code: :sequence_code).limit(1).to_a E, [2019-09-05T16:02:16.952972 #20153] ERROR -- : PG::UndefinedTable: ERROR: relation "access_taggings" does not exist LINE 1: ...."updated_at" FROM "performed_actions" INNER JOIN "access_ta... ^: SELECT <snip> FROM "performed_actions" INNER JOIN "access_taggings" ON ("access_taggings"."performed_action_sequence_code" = "performed_actions"."sequence_code") ORDER BY "performed_actions"."id" LIMIT 1 Sequel::DatabaseError: PG::UndefinedTable: ERROR: relation "access_taggings" does not exist

ตอนนี้คิดว่า :access_taggings เป็นชื่อตารางด้วยเหตุผลบางประการ ได้ เรามาสลับกันกับชื่อตารางจริงกัน

 [10] pry(main)> data = Chronicles::Persistence.relations[:platform][:entries].join(:performed_action_access_taggings, performed_action_sequence_code: :sequence_code).limit(1).to_a => [#<Chronicles::Entities::Entry id=22 subject_g ... updated_at=2012-05-10 08:46:43 UTC>]

ในที่สุดก็ส่งคืนบางสิ่งและไม่ล้มเหลวแม้ว่าจะลงเอยด้วยสิ่งที่เป็นนามธรรมที่รั่ว ชื่อตารางไม่ควรรั่วไหลไปยังรหัสแอปพลิเคชัน

การแก้ไขพารามิเตอร์ SQL

มีคุณลักษณะในการค้นหา Chronicles ซึ่งช่วยให้ผู้ใช้สามารถค้นหาตามส่วนของข้อมูลได้ ข้อความค้นหามีลักษณะดังนี้: {operation: :EQ, path: ["flag", "gid"], value: "gid://plat/Flag/1"} โดยที่ path เป็นอาร์เรย์ของสตริงและค่าเสมอ เป็นค่า JSON ที่ถูกต้องใดๆ

ใน ActiveRecord จะมีลักษณะดังนี้:

 @scope.where('payload -> :path #> :value::jsonb', path: path, value: value.to_json)

ในภาคต่อ ฉันไม่สามารถแก้ไข :path ได้อย่างถูกต้อง ดังนั้นฉันจึงต้องหันไปใช้สิ่งนั้น:

 base_query.where(Sequel.lit("payload #> '{#{path.join(',')}}' = ?::jsonb", value.to_json))

โชคดีที่ path ที่นี่ได้รับการตรวจสอบอย่างถูกต้องเพื่อให้มีเฉพาะอักขระที่เป็นตัวอักษรและตัวเลขคละกัน แต่โค้ดนี้ยังดูตลกอยู่

Silent Magic ของ ROM-factory

เราใช้ rom-factory gem เพื่อทำให้การสร้างแบบจำลองของเราง่ายขึ้นในการทดสอบ อย่างไรก็ตาม โค้ดไม่ทำงานตามที่คาดไว้หลายครั้ง คุณเดาได้ไหมว่ามีอะไรผิดปกติกับการทดสอบนี้

 action1 = RomFactory[:action, app: 'plat', subject_type: 'Job', action: 'deleted'] action2 = RomFactory[:action, app: 'plat', subject_type: 'Job', action: 'updated'] expect(action1.id).not_to eq(action2.id)

ไม่ ความคาดหวังไม่ได้ล้มเหลว ความคาดหวังนั้นดี

ปัญหาคือบรรทัดที่สองล้มเหลวโดยมีข้อผิดพลาดในการตรวจสอบข้อจำกัดเฉพาะ เหตุผลก็คือ action นั้นไม่ใช่แอตทริบิวต์ที่โมเดล Action มี ชื่อจริงคือ action_name ดังนั้นวิธีที่ถูกต้องในการสร้างการกระทำควรมีลักษณะดังนี้:

 RomFactory[:action, app: 'plat', subject_type: 'Job', action_name: 'deleted']

เนื่องจากแอตทริบิวต์ที่พิมพ์ผิดถูกละเว้น คุณลักษณะจะถอยกลับไปเป็นค่าเริ่มต้นที่ระบุไว้ในโรงงาน ( action_name { 'created' } ) และเรามีการละเมิดข้อจำกัดเฉพาะเนื่องจากเรากำลังพยายามสร้างการกระทำที่เหมือนกันสองรายการ เราต้องจัดการกับปัญหานี้หลายครั้งซึ่งพิสูจน์แล้วว่าต้องเสียภาษี

โชคดีที่ได้รับการแก้ไขใน 0.9.0 Dependabot ส่งคำขอดึงมาให้เราโดยอัตโนมัติพร้อมกับการอัปเดตไลบรารี ซึ่งเรารวมเข้าด้วยกันหลังจากแก้ไขแอตทริบิวต์ที่พิมพ์ผิดสองสามรายการที่เรามีในการทดสอบของเรา

การยศาสตร์ทั่วไป

ทั้งหมดนี้กล่าวว่า:

 # ActiveRecord PerformedAction.count _# => 30232445_ # ROM EntryRepository.new.root.count _# => 30232445_

และความแตกต่างนั้นยิ่งใหญ่กว่าในตัวอย่างที่ซับซ้อนกว่า

ส่วนที่ดี

มันไม่ใช่ความเจ็บปวด หยาดเหงื่อ และน้ำตา มีหลายสิ่งที่ดีมากมายในการเดินทางของเรา และสิ่งเหล่านี้มีค่ามากกว่าแง่ลบของกองใหม่ ถ้าไม่เป็นอย่างนั้น เราจะไม่ทำตั้งแต่แรก

ทดสอบความเร็ว

ใช้เวลา 5-10 วินาทีในการรันชุดทดสอบทั้งหมดภายในเครื่อง และนานสำหรับ RuboCop เวลา CI นั้นนานกว่ามาก (3-4 นาที) แต่สิ่งนี้ก็ไม่ค่อยเป็นปัญหาเพราะว่าเราสามารถเรียกใช้ทุกอย่างในเครื่องได้ ต้องขอบคุณสิ่งใดๆ ที่ล้มเหลวใน CI มีโอกาสน้อยกว่ามาก

อัญมณีผู้พิทักษ์กลับมาใช้งานได้อีกครั้ง ลองนึกภาพว่าคุณสามารถเขียนโค้ดและเรียกใช้การทดสอบในการบันทึกแต่ละครั้ง ให้ข้อเสนอแนะที่รวดเร็วมาก เป็นเรื่องยากมากที่จะจินตนาการได้เมื่อทำงานกับแพลตฟอร์ม

เวลาปรับใช้

เวลาในการปรับใช้แอป Chronicles ที่แยกออกมาคือเพียงสองนาที ไม่เร็วปานสายฟ้า แต่ก็ยังไม่เลว เราปรับใช้บ่อยมาก ดังนั้นแม้การปรับปรุงเล็กน้อยก็สามารถประหยัดเงินได้มาก

ประสิทธิภาพของแอพพลิเคชั่น

ส่วนที่เน้นประสิทธิภาพมากที่สุดของ Chronicles คือการค้นหารายการ สำหรับตอนนี้ มีประมาณ 20 แห่งในแบ็คเอนด์ของแพลตฟอร์มที่ดึงรายการประวัติจากพงศาวดาร ซึ่งหมายความว่าเวลาตอบสนองของ Chronicles จะส่งผลต่องบประมาณ 60 วินาทีของแพลตฟอร์มสำหรับเวลาตอบสนอง ดังนั้น Chronicles จึงต้องมีความรวดเร็ว

แม้ว่าจะมีบันทึกการดำเนินการขนาดใหญ่ (30 ล้านแถวและเพิ่มขึ้นเรื่อยๆ) แต่เวลาตอบสนองโดยเฉลี่ยจะน้อยกว่า 100 มิลลิวินาที ดูแผนภูมิที่สวยงามนี้:

แผนภูมิประสิทธิภาพของแอปพลิเคชัน

โดยเฉลี่ยแล้ว 80-90% ของเวลาแอปถูกใช้ในฐานข้อมูล นั่นคือสิ่งที่แผนภูมิประสิทธิภาพที่เหมาะสมควรมีลักษณะเช่นนี้

เรายังมีการสืบค้นที่ช้าซึ่งอาจใช้เวลาหลายสิบวินาที แต่เรามีแผนวิธีกำจัดมันแล้ว ซึ่งช่วยให้แอปที่แยกออกมานั้นเร็วยิ่งขึ้นไปอีก

โครงสร้าง

สำหรับจุดประสงค์ของเรา การตรวจสอบแบบแห้งเป็นเครื่องมือที่ทรงพลังและยืดหยุ่นมาก เราส่งผ่านข้อมูลทั้งหมดจากโลกภายนอกผ่านสัญญา และทำให้เรามั่นใจว่าพารามิเตอร์อินพุตมีรูปแบบที่ดีและกำหนดประเภทไว้อย่างดีเสมอ

ไม่จำเป็นต้องเรียก .to_s.to_sym.to_i ในโค้ดของแอปพลิเคชันอีกต่อไป เนื่องจากข้อมูลทั้งหมดจะถูกล้างและพิมพ์ที่ขอบของแอป ในแง่หนึ่งมันนำความมีสติที่แข็งแกร่งมาสู่โลก Ruby ที่มีพลัง ฉันไม่สามารถแนะนำได้เพียงพอ

คำพูดสุดท้าย

การเลือกสแต็กที่ไม่ได้มาตรฐานนั้นไม่ง่ายอย่างที่คิดในตอนแรก เราได้พิจารณาหลายๆ ด้านในการเลือกเฟรมเวิร์กและไลบรารีที่จะใช้สำหรับบริการใหม่: เทคโนโลยีสแต็กปัจจุบันของแอพพลิเคชั่นโมโนลิธ ความคุ้นเคยของทีมกับสแต็กใหม่ วิธีดูแลรักษาสแต็กที่เลือก และอื่นๆ

แม้ว่าเราจะพยายามตัดสินใจอย่างรอบคอบและคำนวณได้ตั้งแต่ต้น - เราเลือกใช้สแต็ค Hanami มาตรฐาน - เราต้องพิจารณาสแต็คของเราใหม่ตลอดทางเนื่องจากข้อกำหนดทางเทคนิคที่ไม่ได้มาตรฐานของโปรเจ็กต์ เราลงเอยด้วย Sinatra และสแต็กแบบ DRY

เราจะเลือก Hanami อีกครั้งไหมถ้าเราจะแยกแอปใหม่ อาจจะใช่. ตอนนี้เราทราบข้อมูลเพิ่มเติมเกี่ยวกับห้องสมุดและข้อดีและข้อเสียของห้องสมุดแล้ว ดังนั้นเราจึงสามารถตัดสินใจได้อย่างมีข้อมูลมากขึ้นตั้งแต่เริ่มโครงการใหม่ อย่างไรก็ตาม เราจะพิจารณาใช้แอป Sinatra/DRY.rb แบบธรรมดาอย่างจริงจังด้วย

โดยรวมแล้ว เวลาที่ใช้ในการเรียนรู้เฟรมเวิร์ก กระบวนทัศน์ หรือภาษาโปรแกรมใหม่ๆ ทำให้เราได้มุมมองใหม่ๆ เกี่ยวกับสแต็กเทคโนโลยีในปัจจุบัน เป็นการดีเสมอที่จะรู้ว่ามีอุปกรณ์ใดบ้างที่พร้อมใช้งานเพื่อเพิ่มคุณค่าให้กับกล่องเครื่องมือของคุณ เครื่องมือแต่ละอย่างมีกรณีการใช้งานเฉพาะของตัวเอง ดังนั้น การทำความรู้จักกับพวกเขาให้ดีขึ้นหมายถึงการมีมากกว่านั้นไว้ใช้งานและเปลี่ยนให้เหมาะกับการใช้งานของคุณมากขึ้น