วิธีหลีกเลี่ยงการยิงตัวเองที่เท้าโดยใช้ Liquibase

ไม่เคยเกิดขึ้นมาก่อน และเราจะไปอีกครั้ง!

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

บทความนี้ประกอบด้วยเคล็ดลับที่เป็นประโยชน์และคำอธิบายข้อผิดพลาดสามประการที่ชัดเจนที่สุดที่คุณอาจพบเมื่อทำงานกับเครื่องมือการย้ายฐานข้อมูลเชิงสัมพันธ์ โดยเฉพาะ Liquibase ออกแบบมาสำหรับนักพัฒนา Java ในระดับจูเนียร์และกลาง สำหรับนักพัฒนาที่มีประสบการณ์มากขึ้น อาจเป็นที่สนใจสำหรับการจัดโครงสร้างและทำซ้ำสิ่งที่น่าจะทราบอยู่แล้ว

วิธีหลีกเลี่ยงการยิงตัวเองที่เท้าโดยใช้ Liquibase

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

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

ฉันจะไม่ลงลึกเข้าไปในคำอธิบายของเทคโนโลยีและคำแนะนำในการเพิ่มไลบรารีในโครงการของคุณ มีการเขียนบทความบางส่วนในหัวข้อนี้:

นอกจากนี้ยังมีบทความดีๆ ในหัวข้อเคล็ดลับที่เป็นประโยชน์อยู่แล้ว:

Советы

ฉันต้องการแบ่งปันคำแนะนำและความคิดเห็นของฉันซึ่งเกิดจากหยาดเหงื่อ เลือด และความเจ็บปวดในการแก้ปัญหาการย้ายถิ่นฐาน

1. ก่อนเริ่มงาน คุณควรทำความคุ้นเคยกับส่วนแนวทางปฏิบัติที่ดีที่สุด เว็บไซต์ ลิควิเบส

ที่นั่น มีการอธิบายสิ่งที่เรียบง่าย แต่สำคัญมาก โดยที่การใช้ห้องสมุดสามารถทำให้ชีวิตของคุณยุ่งยากได้ ตัวอย่างเช่น แนวทางที่ไม่มีโครงสร้างในการจัดการชุดการเปลี่ยนแปลงไม่ช้าก็เร็วจะทำให้เกิดความสับสนและการโยกย้ายที่เสียหาย หากคุณไม่เปิดตัวการเปลี่ยนแปลงที่ขึ้นต่อกันในโครงสร้างฐานข้อมูลและตรรกะการบริการในเวลาเดียวกัน มีความเป็นไปได้สูงที่จะนำไปสู่การทดสอบสีแดงหรือสภาพแวดล้อมที่เสียหาย นอกจากนี้ คำแนะนำในการใช้ Liquibase บนเว็บไซต์อย่างเป็นทางการยังมีข้อเกี่ยวกับการพัฒนาและการทดสอบสคริปต์การย้อนกลับพร้อมกับสคริปต์การโยกย้ายหลัก ในบทความ https://habr.com/ru/post/178665/ มีตัวอย่างโค้ดเกี่ยวกับการย้ายข้อมูลและกลไกการย้อนกลับ

2. หากคุณเริ่มใช้เครื่องมือการโยกย้าย อย่าอนุญาตการแก้ไขด้วยตนเองในโครงสร้างฐานข้อมูล

ดังคำกล่าวที่ว่า: “เมื่อ Persil, Persil เสมอ” หากฐานของแอปพลิเคชันของคุณเริ่มได้รับการจัดการโดย Liquibase การเปลี่ยนแปลงด้วยตนเองจะนำไปสู่สถานะที่ไม่สอดคล้องกันทันที และระดับความน่าเชื่อถือในชุดการแก้ไขจะกลายเป็นศูนย์ ความเสี่ยงที่อาจเกิดขึ้น ได้แก่ การใช้เวลาหลายชั่วโมงในการกู้คืนฐานข้อมูล ในกรณีที่เลวร้ายที่สุด นั่นก็คือเซิร์ฟเวอร์ที่ไม่ทำงาน หากคุณมีสถาปนิก DBA “แบบเก่า” ในทีมของคุณ ให้อธิบายให้เขาฟังอย่างอดทนและรอบคอบว่าสิ่งที่เลวร้ายจะเป็นอย่างไรหากเขาเพียงแค่แก้ไขฐานข้อมูลตามความเข้าใจของเขาเองจากนักพัฒนา SQL แบบมีเงื่อนไข

3. หากชุดการแก้ไขได้ถูกพุชไปยังพื้นที่เก็บข้อมูลแล้ว ให้หลีกเลี่ยงการแก้ไข

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

4. ตรวจสอบการสำรองฐานข้อมูลหากเป็นไปได้

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

5. ใช้การสำรองฐานข้อมูลที่ได้รับการพิสูจน์แล้วในการพัฒนาหากเป็นไปได้

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

6. สื่อสารกับ Developer คนอื่นๆ ในทีม

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

7. คิดถึงสิ่งที่คุณกำลังทำอยู่!

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

กับดัก

ตอนนี้เรามาดูกับดักทั่วไปที่คุณอาจตกอยู่ในได้หากคุณไม่ปฏิบัติตามคำแนะนำข้างต้น และคุณควรทำอย่างไร?

สถานการณ์ที่ 1: นักพัฒนาสองคนพยายามเพิ่มชุดการแก้ไขใหม่ในเวลาเดียวกัน

วิธีหลีกเลี่ยงการยิงตัวเองที่เท้าโดยใช้ Liquibase
Vasya และ Petya ต้องการสร้างเซ็ตการแก้ไขเวอร์ชัน 4 โดยที่ไม่รู้จักกัน พวกเขาทำการเปลี่ยนแปลงโครงสร้างฐานข้อมูลและออกคำขอดึงข้อมูลด้วยไฟล์เซ็ตการแก้ไขที่แตกต่างกัน เสนอกลไกการดำเนินการดังต่อไปนี้:

ตัดสินใจอย่างไร

  1. เพื่อนร่วมงานจะต้องตกลงตามลำดับที่เซ็ตการแก้ไขควรจะไป เช่น Petin ควรจะถูกนำมาใช้ก่อน
  2. บางคนควรเพิ่มอันที่สองให้กับตัวเองและทำเครื่องหมายเซ็ตการแก้ไขของ Vasya ด้วยเวอร์ชัน 5 ซึ่งสามารถทำได้ผ่าน Cherry Pick หรือการผสานเรียบร้อย
  3. หลังจากการเปลี่ยนแปลง คุณควรตรวจสอบความถูกต้องของการดำเนินการอย่างแน่นอน
    ในความเป็นจริง กลไก Liquibase จะช่วยให้คุณมีชุดการแก้ไขเวอร์ชัน 4 สองชุดในที่เก็บ ดังนั้นคุณจึงสามารถปล่อยทุกอย่างไว้เหมือนเดิมได้ นั่นคือคุณจะมีการเปลี่ยนแปลงสองครั้งในเวอร์ชัน 4 ที่มีชื่อต่างกัน ด้วยวิธีนี้ การนำทางเวอร์ชันฐานข้อมูลจะกลายเป็นเรื่องยากมากในภายหลัง

นอกจากนี้ Liquibase ก็เหมือนกับบ้านของฮอบบิทที่เก็บความลับมากมาย หนึ่งในนั้นคือคีย์ validCheckSum ซึ่งปรากฏในเวอร์ชัน 1.7 และอนุญาตให้คุณระบุค่าแฮชที่ถูกต้องสำหรับชุดการแก้ไขเฉพาะ โดยไม่คำนึงถึงสิ่งที่เก็บไว้ในฐานข้อมูล เอกสารประกอบ https://www.liquibase.org/documentation/changeset.html พูดว่าต่อไปนี้:

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

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

สถานการณ์ที่ 2: การย้ายที่ขึ้นอยู่กับข้อมูล

วิธีหลีกเลี่ยงการยิงตัวเองที่เท้าโดยใช้ Liquibase

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

ในความเป็นจริงสิ่งนี้เป็นไปได้และไม่มีใครรอดพ้นจากสิ่งนี้ สิ่งนี้จะเกิดขึ้นหากการปรับเปลี่ยนโครงสร้างตารางเชื่อมโยงกับข้อมูลเฉพาะจากฐานข้อมูล แน่นอนว่าหากฐานข้อมูลของ Petya เต็มไปด้วยข้อมูลทดสอบเท่านั้น ก็อาจไม่ครอบคลุมทุกกรณีที่เกิดปัญหา ตัวอย่างเช่น เมื่อลบตาราง ปรากฎว่ามีบันทึกในตารางอื่นโดย Foreign Key ที่เกี่ยวข้องกับบันทึกในตารางที่ถูกลบ หรือเมื่อเปลี่ยนประเภทคอลัมน์ปรากฎว่าข้อมูลไม่สามารถแปลงเป็นประเภทใหม่ได้ 100%

ตัดสินใจอย่างไร

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

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

สถานการณ์ที่ 3 Liquibase เริ่มใช้หลังจากเข้าสู่การผลิต

สมมติว่าหัวหน้าทีมขอให้ Petya รวม Liquibase ไว้ในโปรเจ็กต์ แต่โปรเจ็กต์อยู่ระหว่างการผลิตแล้วและมีโครงสร้างฐานข้อมูลอยู่แล้ว

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

ตัดสินใจอย่างไร

นอกจากนี้ยังมีหลายวิธี:

  • สิ่งแรกและชัดเจนที่สุดคือการมีสคริปต์แยกต่างหากที่ต้องใช้ด้วยตนเองเมื่อเริ่มต้นสภาพแวดล้อมใหม่
  • อย่างที่สองไม่ชัดเจนนัก ให้มีการโยกย้าย Liquibase ที่อยู่ในบริบท Liquibase อื่นแล้วนำไปใช้ คุณสามารถอ่านเพิ่มเติมเกี่ยวกับบริบท Liquibase ได้ที่นี่: https://www.liquibase.org/documentation/contexts.html. โดยทั่วไปนี่เป็นกลไกที่น่าสนใจที่สามารถนำไปใช้ได้สำเร็จเช่นในการทดสอบ
  • เส้นทางที่สามประกอบด้วยหลายขั้นตอน ขั้นแรก ต้องสร้างการโยกย้ายสำหรับตารางที่มีอยู่ จากนั้นจะต้องนำไปใช้กับสภาพแวดล้อมบางอย่างและจะได้ผลรวมแฮชของมัน ขั้นตอนต่อไปคือการเริ่มต้นตาราง Liquibase ที่ว่างเปล่าบนเซิร์ฟเวอร์ที่ไม่ว่างเปล่าของเรา และในตารางที่มีประวัติการใช้ชุดการแก้ไข คุณสามารถใส่บันทึกเกี่ยวกับชุดการแก้ไข "ราวกับว่านำไปใช้" ด้วยตนเองพร้อมการเปลี่ยนแปลงที่มีอยู่แล้วในฐานข้อมูล . ดังนั้น บนเซิร์ฟเวอร์ที่มีอยู่ การนับถอยหลังประวัติจะเริ่มจากเวอร์ชัน 2 และสภาพแวดล้อมใหม่ทั้งหมดจะทำงานเหมือนกัน
    วิธีหลีกเลี่ยงการยิงตัวเองที่เท้าโดยใช้ Liquibase

สถานการณ์ที่ 4 การโยกย้ายกลายเป็นเรื่องใหญ่และไม่มีเวลาดำเนินการให้เสร็จสิ้น

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

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

ตัดสินใจอย่างไร

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

ในโหมดสแตนด์อโลน คุณสามารถปล่อยให้การดำเนินการโยกย้ายไปยังสภาพแวดล้อม CI/CD ของคุณ หรือปล่อยให้ผู้ดูแลระบบและผู้เชี่ยวชาญด้านการปรับใช้งานของคุณเป็นผู้มีอำนาจ ในการดำเนินการนี้คุณจะต้องมีบรรทัดคำสั่ง Liquibase https://www.liquibase.org/documentation/command_line.html. ในโหมดนี้ คุณสามารถเปิดแอปพลิเคชันได้หลังจากดำเนินการย้ายข้อมูลที่จำเป็นทั้งหมดแล้ว

เอาท์พุต

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

ที่มา: will.com

เพิ่มความคิดเห็น