Backend developer life in one year @Scale360

Thanyavuth Akarasomcheep
Scale360 Engineering
5 min readJun 6, 2018

เนื่องในโอกาสที่ได้ทำงานที่ Scale360 ครบ 1 ปี จึงอยากรีวิวประสบการณ์ในการเป็น Backend developer ครั้งแรกของผม

จริงๆก่อนที่จะมาเป็น Backend developer ที่ Scale360 ผมเป็น Fullstack developer ที่อื่นมาก่อนแล้ว 1 ปี แต่การเป็นมาทำ Backend ล้วน ทำให้มุมมองในการออกแบบ API เปลี่ยนไปจากมุมมองตอนเป็น Fullstack เยอะพอสมควร คือพอเป็น Backend ต้องออกแบบให้ general ไม่ผูกติดกับหน้าตาของ Frontend UI ใดๆ เพื่อจะได้สามารถ reuse กับ UI อื่นๆ และเมื่อมีการเปลี่ยน UI เราก็ไม่ต้องมาคอยแก้ตามด้วย

ต่อไปจะลง technical เล่าสิ่งที่ผมได้เรียนรู้ใหม่ใน 1 ปี (ลำดับตามที่ได้เรียนรู้)

  • Scala
  • Docker
  • MongoDB
  • Library
  • Authentication (OAuth, Facebook, Google)
  • Microservice Architecture
  • CQRS
  • Kafka/RabbitMQ
  • Server-Sent Events (SSE)
  • SAGA (Microservice Transaction)
  • CI/CD
  • API Gateway (Nginx, Kong)
  • Keycloak

Scala

สำหรับที่นี่จะเขียนเป็น Functional Programming ดังนั้นความรู้ OOP ส่วนใหญ่จะเอามาใช้ไม่ได้ เริ่มแรกคือห้ามมีการเปลี่ยนแปลงค่าของตัวแปร ทุกตัวแปรคือค่าคงที่นั่นแหละ ถ้าอยากเปลี่ยนค่าก็เก็บลงในตัวแปรใหม่ ทำให้ไม่ค่อยมีการคำนวณค่าผิด ตอน debug ไม่ต้องไล่หาว่าค่าผิดเพราะโดนเปลี่ยนไปตอนไหน ต่อมาพวก loop ต่างๆจะเปลี่ยนไปใช้ Map แทน เช่นต้องการให้ค่าทั้งหมดคูณสองก็จะเขียนได้ว่า

val a = List(1, 2, 3)

val b = a.map(x => x * 2) # b = List(2, 4, 6)

นอกจากนี้ยังมีการใช้ functor เช่น Option, Either เพื่อหลีกเลี่ยงการเกิด พวก Runtime exception ได้ เพราะเรามีการเขียนโค้ดให้รองรับทั้งกรณีที่ทำงานได้ผลลัพธ์ปกติ และผลลัพธ์ที่ผิดปกติไว้แล้ว ซึ่งดีกว่าการใช้ try/catch เพราะมันจะผูกติดกับตัวแปรนั้นเลย ทำให้เราไม่ลืมและไม่ต้องคำนึงว่าจุดนี้ต้องมี try/catch หรือไม่

หลังจากเรียนรู้พื้นฐานเสร็จก็ลองทำ API จริงออกมาใช้ดู ข้อเสียของ Scala ที่พบคือ Framework ของภาษานี้มันยังไม่ค่อยดีเลย (เทียบกับ Django ที่เคยใช้มา) และ Library ต่างๆมีเยอะมากจนไม่รู้จะใช้ตัวไหน ก็ต้องไปเล่นหลายๆตัวแล้วเลือกตัวที่ถูกใจกันเอง อย่าง Library สำหรับ Encode/Decode JSON นี่มีเยอะจนเลือกไม่ถูก

สุดท้ายแล้วตอนทำงานจริงทีมผมเลยไม่ใช้ Framework แต่นำ Library ต่างๆมาประกอบใช้เองเลย (แถวนี้แม่งเถื่อน ไม่แน่จริงอยู่ไม่รอด) จริงๆก็อยากรวมออกมาเป็น Framework ไว้นะ เวลาขึ้นโปรเจ็กใหม่จะได้ทำง่ายๆ แต่ยังไม่มีเวลาเลย งานใน Sprint เยอะเกิน ฮ่าๆๆ

สรุป Scala เป็นหนึ่งในภาษาที่ผมชอบ มันลด Logic Error กับ Runtime Error ได้แทบจะหมดไปเลย พอขึ้น Production แล้วเหมือนเจอแค่ Business Logic Bug ที่เรา implement ไม่เป๊ะพอ คือเป็นภาษาที่ Learning curve สูงมาก แต่คล่องแล้วจะติดใจ

Docker

ย้อนกลับไปตอนเข้าทำงานใหม่ หลังจากเขียน Scala ได้แบบงูๆ ปลาๆ ก็ร้อนวิชาอยากลองเขียน API ดู ตอนนั้นไปของานใครก็ไม่ให้ทำ เลยลองทำ TODO List API ด้วย Scala ทีนี่ก็ไปถามพี่

ผม: ใช้ Database ที่ไหนได้ครับ

พี่: อยากใช้ก็สร้างเองสิ

ผม: ไม่มี Server อ่า

พี่: เครื่องตัวเองนั่นแหละ เอา Docker มาลง

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

ตอนนั้นก็โหลด MySQL กับ PostgreSQL มาทำ TODO List ดู คือเลือกไม่ถูกว่าจะใช้อะไร พอได้ Database ก็เขียนโค้ดเพื่อต่อเข้าไปทีนี้แหละ Library มีเยอะจนเลือกไม่ถูกอีกแล้ว ไปถามพี่ๆก็ยังไม่มีคนใช้ ORM แต่เราก็เปรี้ยวอยากใช้เป็น ORM จนไปเจอ Skinny ก็มั่วๆไปและติด เออยอมแพ้ ยอมเปลี่ยนมาใช้ RAW SQL โดยใช้ JDBC ต่อเข้า Database แบบคนอื่น จนทำเสร็จ แล้วก็กลับไปทำ ORM อีกรอบ เห้ยรอบนี้ทำได้แล้ว สรุปทำได้ทั้งแบบ RAW SQL และ ORM เลย เย้

MongoDB

หลังจากนั้นน้องใหม่ 4 คน ก็ถูกจับรวมทีมกัน ได้รับงาน POC มาให้ลองทำเล่นๆ ด้วย data มีลักษณะที่ field ไม่ตายตัว เลยเลือกเก็บข้อมูลแบบ NoSQL ตอนนั้นก็แบบไม่เคยใช้มาก่อน ไม่มี Table แล้วจะใช้ยังไงหว่า จนได้ลองก็อ่อ มันเก็บเป็น JSON นี่เอง สะดวกดีนะ

Library

หลังทำงาน POC เสร็จก็ได้ทำงานใหม่คือให้เขียน Library (นึกในใจ: พึ่งเข้ามาทำเดือนเดียวเองนะ จะเขียน lib ให้ทั้งบริษัทใช้ บ้าไปแล้ว ตายแน่ๆ) ก็ได้ลองสร้าง Library เองครั้งแรกแล้วก็ deploy ขึ้น internal server บริษัท ให้คนอื่นมาดึงไปใช้ ตอน deploy ก็ติด credential นู้นนี่นั่นเยอะแยะ พอทำได้เสร็จก็ต้อง refactor ใหม่ เพราะไม่ควรฝั่ง credential ไว้ใน code ก็ได้เทคนิคใหม่ๆเยอะเลย การใช้ Credential file, Config file, Environment variable

OAuth

ถัดจากการทำ Library ก็ได้งานจริงแล้ว ให้ทำระบบ Login ขึ้นมาใหม่ มีแยกระดับการใช้งานของ User ตามที่สมัครเข้ามา เช่น สมัครแบบ Facebook/Google, สมัครแบบที่ต้องส่งเอกสารให้คนตรวจสอบ ก็ได้ลองเล่น OAuth, JWT, Facebook/Google Token พอทำเสร็จโดยยกเลิกโปรเจ็กซะงั้น เศร้าเลย T_T แต่ก็ไม่เป็นไร เราได้เรียนรู้อะไรมาอีกเยอะเลย

Microservice Architecture

หลังจากนั้นก็ได้งานยักษ์ของจริง คือต้องสร้าง Platform ใหม่ขึ้นมา ทีมเหลือ 3 คนแล้ว อีกคนถูกย้ายไปช่วยทีมอื่น แต่ได้พี่ที่เป็น Specialist มาช่วยแทน กลายเป็นทีม 3 คน กับอีก 1 เทพ สำหรับที่ Scale360 เราใช้ Microservice Architecture ทำให้ได้ออกแบบ Architecture เองด้วย มีการแยก service ออกเป็นส่วนๆตาม Business เพื่อให้ง่ายต่อการ maintenance เวลา business เปลี่ยนเราจะได้เปลี่ยนเฉพาะตัวที่เกี่ยวข้องเท่านั้น ก็มีการใช้เทคนิคต่างๆเพื่อลด dependency ระหว่าง service ซึ่งจะกล่าวต่อไป

CQRS

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

การใช้งาน CQRS เราก็ต้อง implement สิ่งที่เรียกว่า command ว่าเราจะให้มันทำอะไร เช่น ถ้าจะอ่านข้อมูลสินค้าก็เรียก read stock command ถ้าลดจำนวนสินค้าที่ถูกซื้อก็จะเรียก reduce stock command เวลาที่จะแก้ไข logic หรือเปลี่ยน requirement ใหม่ก็จะไม่กระทบกับอีกฝั่ง

รวมถึง model ของฝั่งอ่านและเขียนก็ไม่จำเป็นต้องเหมือนกัน เช่น การลดจำนวนสินค้าจำเป็นต้องรู้แค่ว่าสินค้าไหน กี่ชิ้น Model(id, quantity) แต่การอ่านสินค้าต้องการข้อมูลที่มากกว่า Model(name, stock_quantity, price)

Kafka / RabbitMQ

Platform ใหม่ที่ผมต้องสร้างจำเป็นต้อง streaming realtime ได้ จึงต้องหาตัวช่วยมาใช้นั่นก็คือ Kafka ทำให้ stream ข้อมูลระหว่าง client กับ server หรือแม้แต่ server กับ server ด้วยกันเองก็ตาม

นอกจากนี้ในการ streaming ถ้าเราต้อง streaming เยอะๆ ก็จะต้องเปิดหลาย connection ซึ่งจะเปลือง resource จึงมีการนำ RabbitMQ มาใช้เพื่อรวม streaming หลายๆตัว เข้าด้วยกันแล้วส่งออกไปใน connection เดียว

Server-Sent Events (SSE)

SSE คือการส่งข้อมูลแบบ one way โดยเรานำ Kafka มาประยุกต์ใช้ในการส่งข้อมูลไปหา Service อื่นได้ เช่น

Client → Service A → Service B

จะเห็นได้ว่าเมื่อมีการเรียก Service A จะมีการเรียก Service B ต่อ ดังนั้นถ้าเรารอให้ Service B ทำงานเสร็จแล้วส่ง Response กลับมาที่ Service A จากนั้น Service A จึงส่ง Response กลับไปที่ Client จะทำให้ Client ต้องรอ Response นาน เราจึงแยกการทำงานเป็นแบบ Async

Client → Service A → Kafka → Service B

จะกลายเป็นว่าเมื่อ Service A ถูกเรียก จะมีการส่ง message เข้าไปที่ Kafka ถ้าส่งเข้าไปที่ Kafka สำเร็จ ก็ส่ง response กลับไปหา client ได้เลย ส่วน Service B ก็จะรอฟัง message จาก Kafka เพื่อนำไปทำงาน ทำให้เราสามารถแยกการทำงานของ Service A กับ Service B ออกจากกันได้

การทำงานภายใน Service B สำหรับ service ที่ consume ข้อมูลจาก Kafka มาทำงาน เราจะ implement ในรูปแบบกราฟ คือข้อมูลที่มาจาก Kafka จะวิ่งเข้ามาในกราฟแล้วผ่าน logic ต่างๆ จากนั้นจะถูกคัดแยกออกเป็นกลุ่มต่างๆที่เรียกว่า sink เราก็จะนำข้อมูลใน sink ต่างๆไปใช้งานต่อไป

หลายคนก็จะมีคำถามในใจถ้า Service B ทำงานแล้ว Fail ขึ้นมา Response ที่ Service A ส่งไปบอกว่าสำเร็จก็ไม่ถูกต้องสิ ใช่แล้ว เพราะนี่คือการ implement แบบ SSE ที่เป็น one way จึงไม่ควรมี response กลับไปตั้งแต่แรก แต่ในการเรียก API ก็ควรมี response ดังนั้น response จึงบอกได้แค่ว่าที่เรียกเข้ามานั้นระบบรับรู้แล้วนะ หรือว่า Error นะ ที่เรียกมาไม่ถึงระบบนะ จึงมีการแก้ไขโดยการนำ CQRS มาใช้ร่วมด้วย คือจะแยก API ในการสั่งการ กับ API ในการอ่านผลลัพธ์ออกจากกัน เช่น

Client → Service Buy → Kafka → Service Stock

การซื้อสินค้าที่ไม่มีของเหลือ เมื่อซื้อสินค้าก็เรียก Post ไปที่ Buy product API จากนั้นถ้า server ได้รับคำสั่งซื้อนั้นเรียบร้อยก็จะส่ง response กลับไปบอกว่าได้รับคำสั่งซื้อสำเร็จ หรือถ้ามีปัญหาล่มก็จะได้ error กลับไปแทน

แต่ที่ Service Stock พบว่าสินค้าหมดแล้ว ดังนั้น client ควรจะรู้ว่าคำสั่งซื้อสินค้าที่ส่งไปนั้นซื้อไม่สำเร็จ client ก็จะเรียก Get Buy report API เพื่อดูสถานะได้ว่าสั่งซื้อสำเร็จจริงหรือไม่

ก็จะเกิดอีกปัญหาหนึ่งตามมาคือ ถ้า client เรียกดูสถานะสั่งซื้อก่อนที่ Service Stock จะทำเสร็จอาจได้ fail แต่เมื่อ Service Stock ทำเสร็จจะกลายเป็น success ซะอย่างนั้น จึงเลี่ยงปัญหาได้โดย client ไม่ต้องเรียก Get Buy report API เอง แต่ stream เอาแทน ถ้า Service Stock ทำงานออกมาเสร็จจะส่ง response กลับไปบอกเองว่าซื้อได้หรือไม่ได้

แล้ว Buy report API จะใช้เมื่อไร ก็เมื่อ client เปิดแอพขึ้นมาใหม่ แล้วเรียกข้อมูลครั้งแรกตอนเปิดแอพเพื่อดึงข้อมูลเก่าๆออกมาแสดง หลังจากนั้นก็จะได้รับข้อมูลใหม่ๆทาง stream อยู่แล้ว

SAGA

การทำงานในระบบที่มี service เดียว ถ้ามีการทำงานผิดพลาดเกิดขึ้น สามารถใช้ Transaction เพื่อ restore ค่าต่างๆ กลับเป็นเหมือนเดิมได้ แต่สำหรับ Microservice จะทำ Transaction ระหว่าง service ได้ด้วย Saga pattern เช่น Service A เรียกใช้งาน Service B และ Service A เรียกใช้งาน Service C ด้วย

ถ้าหาก Service B หรือ Service C มีตัวใดตัวหรือทำไม่สำเร็จจะต้องย้อนผลการทำงานของอีก service ให้ข้อมูลกลับเป็นเหมือนเดิม เราสามารถ implement เป็น Rest API ให้เรียกใช้งานสำหรับสั่งย้อนการทำงานได้ หรือจะ implement ด้วย SSE และส่ง events ไปบอกให้ย้อนการทำงานก็ได้

CI/CD

เนื่องจาก Platform ที่ได้พัฒนาเป็นระบบที่ใหญ่มาก ทำให้เรามีจำนวน service ประมาณ 40 services พี่ก็บอกมาว่า server กลาง resource ไม่พอนะ เดี๋ยวหาเครื่องให้แล้วไปจัดการ deploy เองนะ

ได้ฟังก็น้ำตาคลอ ต้องทำเองหมดเลย แงงงงง

ชีวิตก็ต้องดิ้นกันต่อไปบอกแล้วแถวนี้แม่งเถื่อน ฮ่าๆๆ

อันดับแรกก็ต้องเขียน Dockerfile ก่อน แล้วก็มี Entrypoint ด้วย จากนั้นตอนจะ deploy จริงก็มีการทำ docker-compose แถมอีกตัว ยัง ยังไม่พอ ตอนจะเอาขึ้นก็ได้จับ Jenkins ครั้งแรก ไปแอบก็อปๆ ของคนอื่นมาทำตาม สุดท้ายแล้วก็เอาขึ้นจนได้ เย้

นอกจากนั้นตอน release ขึ้น production ก็โดนตีกลับให้ไปแก้ Config file ใหม่ ให้รองรับกับ Infra Environment หลายๆแบบได้ ก็ได้รู้เพิ่มขึ้นว่าควรเขียน Config แบบไหนถึงจะดี แล้วก็ได้ลองเอา images ขึ้น AWS เองอีกด้วย

การพี่ให้ทำเองเป็นอะไรที่ดีมาก เราได้เรียนรู้อะไรใหม่ๆ อีกเยอะเลย และก็สามารถทำเองได้แล้ว :)

API Gateway (Nginx, Kong)

สมาชิกในทีมก็โดนย้ายไปช่วยทีมอื่นอีกแล้ว ทำให้เหลืออยู่ 2 คน กับ 1 เทพ หลังจาก deploy ขึ้นแล้วทีนี้ก็ต้องมาทำ API Gateway เพื่อให้ API Pattern เป็นไปในแนวทางเดียวกัน แล้วไม่มีการชนกันเพราะแต่ละ service อาจมี URL ซ้ำกันได้ ก็มีการนำ Kong เข้ามาใช้งานเป็นตัว Mapping API ระหว่าง Public API ที่จะให้คนอื่นเรียกใช้งาน กับ API จริงๆ ของ service

Platform ที่ทำนั้นมีการ streaming แบบ SSE ซึ่ง Kong ไม่รองรับหรือ config ไม่ถูกก็ไม่รู้ เลยนำ Nginx เข้ามาเป็น API Gateway สำหรับ Streaming API แล้วก็ map ตัวที่ไม่ใช่ streaming ให้ไปที่ Kong เหมือนเดิม

นอกจากนี้ยังใช้ Nginx ช่วยในการทำ HTTPS และ HTTP/2 เพื่อทำ Multiplex connection ด้วย เพราะถึงแม้เราจะรวม streaming ด้วย RabbitMQ แต่ถ้า Client ต่อเข้ามาหลาย connection สุดท้ายก็จะติด limited อยู่ดี

Keycloak

หลังจากทำ Platform เสร็จ deploy เรียบร้อย ก็มาถึงการรวมเข้ากับตัวอื่น ทีนี้เราต้องการให้ระบบสามารถใช้งานแบบ Single sign-on แต่เราไม่อยากทำระบบ Authentication ขึ้นมาเองแล้ว เลยไปเอา Keycloak มาใช้ ก็สามารถจัดการพวก token และ permission ใด้ด้วย

ส่งท้าย

นอกจากนี้ สำหรับทีมเราเวลา Sprint planing ก็ได้กำหนด story ที่จะทำใน sprint เอง แต่ก็ยึดตาม priority ที่ Project Manager กับ Team Lead บอกมานั่นแหละ ทั้งหมดที่พูดมานี้เป็นประสบการณ์ที่เกิดขึ้นใน 1 ปี เรียนรู้ technical ใหม่ๆ เยอะมาก ได้ดูเรื่อง infra กับ environment เองนิดหน่อย ได้ทำ CI/CD เอง ได้ออกแบบ Architecture และ Service ต่างๆ ต้องบอกว่าเข้มข้นมากจริงๆ สนุกและท้าทาย ได้เล่นอะไรใหม่ๆตลอดเวลา ทีมนี้เป็นทีมที่เข้มข้นมากจริงๆ เราสร้างขึ้นมาใหม่ถึง 40 services ก็ 1 ใน 3 ของทั้งบริษัทเอง อะไรที่ทีมอื่นยังไม่ใช้ทีมนี้ก็เอามาลองของก่อน ^^;

ขอบคุณทีมทั้ง 4 คน และ 1 เทพ ในการทำงาน 1 ปีที่ผ่านมา

ขอบคุณเพื่อนๆ พี่ๆ น้องๆ ทีมอื่นที่เวลาช่วยเหลือเวลาไปถาม

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

ใครอยากลองอยากเล่นอะไรใหม่ๆ แปลกๆ ก็เข้ามา

คุณจะไม่ได้แค่โค้ดไปวันๆ อย่างเดียวแน่นอน

Contact Center & Chat Team

--

--