[৮.০] প্রোডাকশনে গো (Go, in production)
বুটক্যাম্পের রোডম্যাপ টা ফলো করতে করতে এতদূর যেহেতু এসে পড়েছি, সুতরাং আশা করা যাচ্ছে Go এর ব্যাপারে আমাদের মোটামুটি ভালো একটা বোঝাপড়া হয়ে গিয়েছে। কোন একটা ল্যাংগুয়েজে কতটুকু হাতে নিয়ে আসতে পেরেছি সেটার সবথেকে বড় প্রমাণ পাওয়া যায় সেটা কোন প্রজেক্টে ব্যবহার করে। এতে করে বাস্তব জগতে আমরা কেমন সমস্যার সম্মুখীন হতে পারি সেটা সম্পর্কেও একটা ভালো ধারনা পাওয়া যায়। এছাড়াও, সঠিক গাইডলাইন পেয়ে এমন প্রজেক্ট করলে নিজে থেকে পরবর্তীতে কোনো প্রজেক্ট হাতে নেয়ার ক্ষেত্রেও একটা আত্মবিশ্বাস কাজ করবে। সেই আত্মবিশ্বাস টা হাতের নাগালে এনে দেয়াই বুটক্যাম্পের এই অংশের লক্ষ্য।
[৮.১] কেনো ECHO? (Why ECHO)
- স্কেলেবিলিটি ও পারফরম্যান্স।
- লগিং, নিরাপত্তা, অথেনটিকেশনের মতো প্রয়োজনীয় ও সাধারণ বিষয়গুলোর জন্য Middleware এর উপস্থিতি।
- যারা স্ক্র্যাচ থেকে API তৈরি করা শিখতে চাই তাদের জন্য এটি দুর্দান্ত। কেননা, গিটহাবে ২৫ হাজার স্টার সম্বলিত এই ফ্রেমওয়ার্কের একটি বিশাল ইউজার বেস আছে এবং সাথে আছে সাজানো, গুছানো, নিবেদিত ডকুমেন্টেশন।
ফ্রেমওয়ার্কের অনেক গভীরে আমরা প্রবেশ করব না। কিন্তু আশা করা যায় এই প্রজেক্টের শেষে কোন পথে আগাতে হবে সেটা নিয়ে কোনো দ্বিধা থাকবে না। ভবিষ্যৎ শেখার স্কোপ গুলো সম্পর্কে একটা ধারণা দিয়েই বুটক্যাম্পের সমাপ্তি টানবো।
- ECHO ব্যবহার করে ওয়েব সার্ভার তৈরি (Building a web server using ECHO)
- যেহেতু এটি একটি ছোট প্রজেক্ট হতে যাচ্ছে, সুতরাং এর জন্য main.go নামের একটি ফাইল তৈরি করাই যথেষ্ট।
- এবারে আমাদের যে কাজটি করতে হবে সেটি হচ্ছে ডিপেন্ডেনসি ম্যানেজমেন্ট। এর জন্য প্রজেক্ট ডিরেক্টরি তে ঢুকেই নিচের কমান্ডটি টার্মিনালে রান করতে হবে –
go mod init bootcamp-webserver
এর ফলে একটি go mod এবং go sum ফাইল তৈরি হবে যেখানে আমাদের প্রয়োজনীয় প্যাকেজগুলোর ট্র্যাক থাকবে।
3. এখন আমরা ECHO ফ্রেমওয়ার্ক ব্যবহারের জন্য, এর প্যাকেজটি নামাবো। এর জন্য আমাদের নিচের কমান্ডটি টার্মিনালে রান করতে হবে –
go get github.com/labstack/echo/v4
4. এখন আমরা আমাদের main.go ফাইলের অভ্যন্তরে, main() ফাংশনের মধ্যে echo এর একটি ইন্সট্যান্স তৈরি করি।
- ECHO পরিচিতি :
একটা ছোট্ট সাইড মিশনে যাচ্ছি ECHO ফ্রেমওয়ার্কের কিছু বেসিকের সাথে পরিচিত হতে যা আমাদের আপাতত বুঝে শুরু হতে সাহায্য করবে।
উপরের echo.New() ফাংশন কলের মাধ্যমে একটি echo.Echo ইন্সট্যান্সের পয়েন্টার রিটার্ন পাওয়া যায়, যেটা এই প্রজেক্টের শুরুর জায়গা হিসেবে কাজ করে। অর্থাৎ প্রজেক্টের সব routes, middleware এবং HTTP রিকোয়েস্ট ও রেসপন্স নিয়ে কাজের জন্য এই echo.Echo ইন্সট্যান্স ব্যবহৃত হয়। যেমন, echo.GET(), echo.GROUP(), echo.Context() ইত্যাদি ফাংশন। এদের ব্যবহার সামনে যেয়ে আমরা দেখতে পাবো। আবার তাহলে কোডে ফেরত যাওয়া যাক।
5. একটি Route তৈরি করি GET মেথড ব্যবহার করে, যাতে আমরা কুয়েরি প্যারামিটার হিসেবে ২ টি সংখ্যা পাঠাবো। একটি Route এ যে হ্যান্ডলার ফাংশনটি ব্যবহৃত হবে, সেটিতে echo.Context অবজেক্টটি চলে যাবে এবং এই Context অবজেক্টের মধ্যেই রিকোয়েস্টের সকল ইনফরমেশন উপস্থিত থাকবে। যেমন, প্যারামিটার ভ্যালু, URL.
6. এই Route এর হ্যান্ডলার ফাংশন হিসেবে, এই প্যাকেজের মধ্যেই Calculation() ফাংশনটি তৈরি করি। তাহলে আমাদের main() ফাংশনটি দাঁড়াচ্ছে –
func main() {
/* New Echo instance */
e := echo.New()
/* Route with GET method*/
e.GET("/calculate", Calculation)
}
7. এখন একটি Struct তৈরি করব Result নামের। এর মধ্যে আমরা যোগফল, গুণফল , বিয়োগফল এবং ভাগফলের ভ্যালুগুলো স্টোর করব এবং সবশেষে রিটার্ন করব –
type Result struct {
Addition int
Subtraction int
Multiplication int
Division int
}
8. এবারে পালা হ্যান্ডলার ফাংশনটি তৈরি করার। প্রথমেই কুয়েরি প্যারামিটার থেকে ২ টি ভ্যালু num1 এবং num2 এর ভ্যালু নিয়ে সেগুলো পার্স করে ইন্টিজার হিসেবে a এবং b তে স্টোর করব –
হ্যান্ডলার ফাংশন এবং echo.Context অবজেক্ট পরিচিত
এখানে প্রত্যেকটি হ্যান্ডলার ফাংশনে আমরা echo.Context নামক অবজেক্ট পাস করেছি। এই অবজেক্টের মধ্যে সর্বশেষ HTTP রিকোয়েস্টের রিকোয়েস্ট/রেসপন্স অবজেক্ট, রিকোয়েস্ট হেডার, URL এর তথ্যাদি, প্যারামিটার, কুয়েরি অথবা ফর্ম ডাটা থাকে এবং হ্যান্ডলার ফাংশন এগুলো ব্যবহার করে রেসপন্স, ভ্যলিডেশন , অথেনটিকেশন, লগিং এর মতো যাবতীয় কাজ করতে পারে। আমরাও এরকম কিছু পরবর্তীতে বিস্তারিত করব। আপাতত আমরা ইন্টারফেসের ব্যবহারের সাথে পরিচিত হবো।
func Calculation(c echo.Context) error {
num1 := c.QueryParam("num1")
a, err := strconv.Atoi(num1)
if err != nil {
return c.JSON(http.StatusInternalServerError, err)
}
num2 := c.QueryParam("num2")
b, err := strconv.Atoi(num2)
if err != nil {
return c.JSON(http.StatusInternalServerError, err)
}
}
9. এবারে যোগফল, গুণফল , বিয়োগফল এবং ভাগফলের Result এর একটি ইন্সট্যান্সে স্টোর করব –
result := Result{
Addition: a + b,
Subtraction: a - b,
Multiplication: a * b,
Division: a / b,
}
10. সবশেষে result কে JSON আকারে রিটার্ন করে দেবো এবং main() ফাংশন থেকে সার্ভার start করার মাধ্যমে ওয়েব সার্ভার তৈরির কাজটি সম্পন্ন করব। তাহলে আমাদের সম্পূর্ণ ওয়েব সার্ভারের কোডটি দাঁড়াচ্ছে –
package main
import (
"net/http"
"strconv"
"github.com/labstack/echo/v4"
)
type Result struct {
Addition int
Subtraction int
Multiplication int
Division int
}
func Calculation(c echo.Context) error {
num1 := c.QueryParam("num1")
a, err := strconv.Atoi(num1)
if err != nil {
return c.JSON(http.StatusInternalServerError, err)
}
num2 := c.QueryParam("num2")
b, err := strconv.Atoi(num2)
if err != nil {
return c.JSON(http.StatusInternalServerError, err)
}
result := Result{
Addition: a + b,
Subtraction: a - b,
Multiplication: a * b,
Division: a / b,
}
return c.JSON(http.StatusOK, result)
}
func main() {
/* New Echo instance */
e := echo.New()
/* Route with GET method*/
e.GET("/calculate", Calculation)
// web server start
e.Logger.Fatal(e.Start(":8080"))
}
Postman এর ব্যবহার –
“localhost:8080/calculate” route এ কুয়েরি প্যারামিটার সহ পাঠানো GET রিকোয়েস্টের ফাইনাল গঠনটি হলো – “localhost:8080/calculate?num1=5&num2=6”
রেসপন্স –
{
"Addition": 11,
"Subtraction": -1,
"Multiplication": 30,
"Division": 0
}
ECHO ব্যবহার করে একদম বেসিক CRUD ফাংশনালিটি তৈরি (Basic CRUD with ECHO)
উপরের ওয়েব সার্ভারটি বানিয়ে আমরা ECHO ব্যবহার করে Route তৈরি, হ্যান্ডলার ফাংশনের ব্যবহার ও সার্ভার স্টার্ট করার মতো মৌলিক বিষয়গুলোর সাথে পরিচিত হয়েছি। যেহেতু বুটক্যাম্পের এই অংশে আমরা একটি প্রোডাকশন পর্যায়ের CRUD প্রজেক্ট করব, সেহেতু এর আগে আমাদের একদম সাধারণভাবে CRUD এর কি কিছু ফাংশনালিটির প্রয়োগ সম্পর্কে ধারণা লাভ করা উচিত। এতে করে আমরা প্রজেক্ট স্ট্রাকচারের প্রয়োজনীয়তা আরো ভালোভাবে বুঝতে পারব এবং একই সাথে আমাদের এটাও মনে হবে না যে আমাদের শেখার পথে কিছু বাদ পড়েছে।
এই প্রজেক্টে আমরা এক ফাইলের ভেতরেই ডাটাবেসে টেবিল, সার্ভার এবং সাথে User এর Create, Read এবং Delete করার জন্য হ্যান্ডলার ফাংশন তৈরি করব এবং JSON হিসেবে রেসপন্স রিটার্ন করব। তাহলে আর দেরি না করে শুরু করা যাক –
- শুরুতেই, কাজ করতে হচ্ছে ডিপেন্ডেনসি ম্যানেজমেন্ট নিয়ে। এর জন্য প্রজেক্ট ডিরেক্টরি তে ঢুকেই নিচের কমান্ডটি টার্মিনালে রান করতে হবে –
go mod init simple-functionality
- এই প্রজেক্টে যেহেতু আমরা GORM এবং MySQL ব্যবহার করে কাজ করব। সুতরাং শুরুতে আমাদের এই প্যাকেজ দুটি নামিয়ে নিতে হবে। এর জন্য নিচের কমান্ড দুটি টার্মিনালে রান করব –
go get gorm.io/gorm
go get -u gorm.io/driver/mysql
- এবারে আমরা ডাটাবেসে কানেক্ট করব। এজন্য একটি কানেকশন string তৈরি করে প্রয়োজনীয় সকল তথ্য পাস করে দেবো এবং এই অনুযায়ী *gorm.DB এর একটি ইন্সট্যান্স তৈরি হবে। এই কাজটি main() ফাংশনের অভ্যন্তরে করব। তার আগে *gorm.DB এর একটি এম্পটি ভ্যারিয়েবল গ্লোবালি তৈরি করে রাখব। যাতে আমরা আমাদের প্রাপ্ত ইন্সট্যান্সটি গ্লোবালি স্টোর করতে পারি-
var DB *gorm.DB
func main() {
e := echo.New()
// Initiate DB
dsn := "root:191491@tcp(127.0.0.1:3306)/basiccrud?charset=utf8mb4&parseTime=True&loc=Local"
d, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
if err != nil {
panic(err)
}
DB = d
}
- একটি Struct তৈরি করে নেই যেটা আমাদের ডাটাবেস টেবিল স্কিমা হিসেবে কাজ করবে। এই কাজটি আমরা এর আগে ORM অংশে শিখে এসেছি –
type User struct {
ID int `gorm:"primaryKey" json:"id"`
Name string `json:"name"`
Age int `json:"age"`
}
- এবারে GORM এর অটো মাইগ্রেশন ব্যবহার করে টেবিলটি তৈরি করব –
// Create and migrate db
DB.AutoMigrate(&User{})
- আমাদের Route গুলো তৈরি করে নেবো এবং হ্যান্ডলার ফাংশন গুলো অ্যাসাইন করে দেবো –
// Routes
e.GET("/users", GetAllUsers)
e.POST("/users", CreateUser)
e.DELETE("/users/:id", DeleteUser)
- প্রথমেই কাজ শুরু করব CreateUser() ফাংশন নিয়ে। নতুন User তৈরি করার জন্য প্রয়োজনীয় যে ইনপুট গুলো আছে সেগুলো User টাইপের একটি ইন্সট্যান্সে বাইন্ড করে নেবো এবং GORM এর Create() মেথড ব্যাবহার করে নতুন User তৈরি করে একটি সাকসেস রেসপন্স রিটার্ন করবে। মাঝে কোনো এররের সম্মুখীন হলে তা সাথে সাথে রেসপন্সে পাঠিয়ে দেবো –
func CreateUser(c echo.Context) error {
user := &User{}
if err := c.Bind(user); err != nil {
return c.JSON(http.StatusBadRequest, err.Error())
}
if err := DB.Create(&user).Error; err != nil {
return c.JSON(http.StatusInternalServerError, err.Error())
}
return c.JSON(http.StatusCreated, "User created successfully")
}
- এরপর কাজ করব GetAllUsers() ফাংশনটি নিয়ে। শুরুতেই একটি User টাইপের অ্যারে নেবো, যেটায় আমাদের রেসপন্সে আসা সকল User এর রেকর্ড রাখা হবে। GORM এর Find() মেথড ব্যাবহার করে সকল User এর রেকর্ড users অ্যারে তে রাখব এবং রেসপন্স হিসেবে User এর এই কালেকশনটি রিটার্ন করব। মাঝে কোনো এররের সম্মুখীন হলে তা সাথে সাথে রেসপন্সে পাঠিয়ে দেবো –
func GetAllUsers(c echo.Context) error {
var users []User
if err := DB.Find(&users).Error; err != nil {
return c.JSON(http.StatusInternalServerError, err.Error())
}
return c.JSON(http.StatusOK, users)
}
- সবশেষে আমরা কাজ করব DeleteUser() ফাংশনটি নিয়ে। প্রথমেই প্যারামিটার ভ্যালু থেকে id এর ভ্যালু নিয়ে তা পার্স করে ইন্টিজার হিসেবে ID তে স্টোর করব। GORM এর Delete() মেথড ব্যাবহার করে উক্ত ID সম্বলিত User কে টেবিল থেকে ডিলেট করে দেবো এবং রিটার্ন হিসেবে একটি সাকসেস রেসপন্স পাঠাবো। মাঝে কোনো এররের সম্মুখীন হলে তা সাথে সাথে রেসপন্সে পাঠিয়ে দেবো –
func DeleteUser(c echo.Context) error {
var user User
id := c.Param("id")
ID, err := strconv.ParseInt(id, 0, 0)
if err != nil {
return c.JSON(http.StatusInternalServerError, err.Error())
}
if err := DB.Where("id = ?", ID).Delete(&user).Error; err != nil {
return c.JSON(http.StatusInternalServerError, err.Error())
}
return c.JSON(http.StatusOK, "User deleted successfully")
}
- এবারে main() ফাংশনের ভেতর থেকে সার্ভারটি স্টার্ট করার মাধ্যমে আমাদের এই প্রজেক্টটি শেষ হবে –
// Start server
e.Logger.Fatal(e.Start(":1111"))
- তাহলে আমাদের পুরো প্রজেক্টটি দাঁড়াচ্ছে –
package main
import (
"net/http"
"strconv"
"github.com/labstack/echo/v4"
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
type User struct {
ID int `gorm:"primaryKey" json:"id"`
Name string `json:"name"`
Age int `json:"age"`
}
var DB *gorm.DB
func CreateUser(c echo.Context) error {
user := &User{}
if err := c.Bind(user); err != nil {
return c.JSON(http.StatusBadRequest, err.Error())
}
if err := DB.Create(&user).Error; err != nil {
return c.JSON(http.StatusInternalServerError, err.Error())
}
return c.JSON(http.StatusCreated, "User created successfully")
}
func GetAllUsers(c echo.Context) error {
var users []User
if err := DB.Find(&users).Error; err != nil {
return c.JSON(http.StatusInternalServerError, err.Error())
}
return c.JSON(http.StatusOK, users)
}
func DeleteUser(c echo.Context) error {
var user User
id := c.Param("id")
ID, err := strconv.ParseInt(id, 0, 0)
if err != nil {
return c.JSON(http.StatusInternalServerError, err.Error())
}
if err := DB.Where("id = ?", ID).Delete(&user).Error; err != nil {
return c.JSON(http.StatusInternalServerError, err.Error())
}
return c.JSON(http.StatusOK, "User deleted successfully")
}
func main() {
e := echo.New()
// Initiate DB
dsn := "root:191491@tcp(127.0.0.1:3306)/basiccrud?charset=utf8mb4&parseTime=True&loc=Local"
d, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
if err != nil {
panic(err)
}
DB = d
// Create and migrate db
DB.AutoMigrate(&User{}
// Routes
e.GET("/users", GetAllUsers)
e.POST("/users", CreateUser)
e.DELETE("/users/:id", DeleteUser)
// Start server
e.Logger.Fatal(e.Start(":1111"))
}
Postman এর ব্যবহার –
Create এর জন্য –
“localhost:1111/users” route এ নিচের json ভ্যালুটি ব্যবহার করে POST রিকোয়েস্ট দেয়ার মাধ্যমে, আমরা Create ফাংশনালিটি ঠিকঠাক শেষ করতে পেরেছি কিনা তা চেক করতে পারি –
{
"name": "Raisul Islam",
"age": 23
}
যদি আমাদের কোড ঠিকঠাক থাকে তাহলে Postman এ রেসপন্স আসবে “User created successfully“
রেফারেন্স হিসেবে নিচের ছবি টা দেখতে পারি –
Read এর জন্য –
“localhost:1111/users” route এ GET রিকোয়েস্ট পাঠিয়ে আমরা Read ফাংশনালিটি ঠিকঠাক শেষ করতে পেরেছি কিনা তা চেক করতে পারি।
রেসপন্স –
[
{
"id": 1,
"name": "Raisul Islam",
"age": 23
},
]
রেফারেন্স হিসেবে নিচের ছবি টা দেখতে পারি –
Delete এর জন্য –
“localhost:1111/users/1” route এ DELETE রিকোয়েস্ট পাঠিয়ে আমরা DELETE ফাংশনালিটি ঠিকঠাক শেষ করতে পেরেছি কিনা তা চেক করতে পারি –
যদি আমাদের কোড ঠিকঠাক থাকে তাহলে Postman এ রেসপন্স আসবে “User deleted successfully” এবং আমাদের ID = 1 ইউজারের সব ভ্যালু Delete হয়ে যাবে ।
রেফারেন্স হিসেবে নিচের ছবি টা দেখতে পারি –
প্রজেক্টের ধাপগুলো বর্ণনার সময়, কিছু বিষয়ে বিস্তারিতভাবে ব্যাখ্যা দেয়া হয়নি। কেননা আমরা এসকল বিষয় বুটক্যাম্পের বিভিন্ন অংশে জেনে এসেছি। আশা করছি, এই প্রজেক্টটি করার মাধ্যমে পরবর্তী প্রজেক্টের কাজগুলো আমাদের কাছে আরো সহজ এবং বোধগম্য হবে। তাহলে শুরু করছি আমাদের বুটক্যাম্পের প্রধান এবং সর্বশেষ প্রজেক্টটি…