গো সার্ভিসের সঠিক Go Project Structure: কীভাবে সাজাবেন?

নতুন কোনো গো প্রোজেক্টে pkg/ ডিরেক্টরি দেখলেই আমি অনেকটা নিশ্চিত হয়ে যাই যে এটা এক সময় জাঙ্ক ড্রয়ারে পরিণত হবে। মানুষ সাধারণত pkg/-কে পাবলি ফোল্ডার হিসেবে ব্যবহার করে যাতে পরে কিছু শেয়ার করা যায়, কিন্তু বাস্তবে তা কখনও ঘটে না। বরং সেখানে আজেবাজে হেল্পার ফাংশন জমে পুরো প্রোজেক্টে এক বিশৃঙ্খলা তৈরি করে। আমি আমার প্রোজেক্টগুলোতে প্রায় সবকিছুই internal/ ফোল্ডারের ভেতর রাখি যাতে ভুলবশত কোনো অপ্রয়োজনীয় কাপলিং তৈরি না হয়।

ম্যানুয়ালি ওয়্যারিং: ফ্রেমওয়ার্ক কি আসলেই দরকার?

অনেকদিন ধরে Uber এর fx বা Google's wire এর মতো DI (Dependency Injection) ফ্রেমওয়ার্কগুলো ব্যবহার করা বন্ধ করে দিয়েছি। অনেকে হয়তো বলবে, "এগুলো তো জাস্ট বয়লারপ্লেট কমায়"。 কিন্তু আমার কাছে ৩০ লাইনের ম্যানুয়াল স্ট্রাক্ট ইনিশিয়ালাইজেশন অনেক বেশি স্বস্তিদায়ক। কোন প্রোভাইডার কেন রেজিস্টার হয়নি বা রিফ্লেকশন-বেসড কনটেইনার কেন ক্রিপ্টিক এরর দিচ্ছে—এসব নিয়ে ২০ মিনিট ডিবাগ করার চেয়ে সরাসরি কোড দেখাই ভালো।

main.go ফাইলটা দেখলেই আপনি পুরো ডিপেন্ডেন্সি গ্রাফ পরিষ্কার দেখতে পাবেন। কোনো ম্যাজিক নেই। কিছু যদি মিস হয়, কম্পাইলারই বলে দেবে।

আমি যে লেআউট ব্যবহার করি

এটি কোনো বিপ্লবী লেআউট নয়, তবে যথেষ্ট ফ্ল্যাট যাতে ফোল্ডার গোলকধাঁধায় হারিয়ে না যেতে হয়।

.
├── cmd/service/main.go  # ওয়্যারিংয়ের মূল জায়গা
├── internal/
│   ├── api/             # ট্রান্সপোর্ট লেয়ার (HTTP/gRPC)
│   ├── domain/          # ইন্টারফেস এবং কন্ট্রাক্ট
│   ├── logic/           # বিজনেজ লজিক বা ব্রেইন
│   ├── store/           # পারসিস্টেন্স বা ডাটাবেস
│   └── config/          # এনভায়রনমেন্ট কনফিগারেশন
└── Makefile             # রিয়েল লাইফ ইউআই.
├── cmd/service/main.go  # ওয়্যারিংয়ের মূল জায়গা
├── internal/
│   ├── api/             # ট্রান্সপোর্ট লেয়ার (HTTP/gRPC)
│   ├── domain/          # ইন্টারফেস এবং কন্ট্রাক্ট
│   ├── logic/           # বিজনেজ লজিক বা ব্রেইন
│   ├── store/           # পারসিস্টেন্স বা ডাটাবেস
│   └── config/          # এনভায়রনমেন্ট কনফিগারেশন
└── Makefile             # রিয়েল লাইফ ইউআই

হ্যান্ড-ক্রাফটেড SQL এর আনন্দ ও যন্ত্রণা

আমি ORM বা SQL জেনারেটর যতটা সম্ভব এড়িয়ে চলি। sqlc ট্রাই করেছিলাম, কিন্তু কমপ্লেক্স জয়েন বা Postgres এর স্পেসিফিক ফিচার নিয়ে ডিল করতে গিয়ে বারবার পার্সারের সাথে ফাইট করতে হয়েছে।

র-এসকিউএল (Raw SQL) লেখা আর স্ট্রাক্টে ম্যাপ করা অনেক বেশি ফ্লেক্সিবল। মাঝেমধ্যে গো-এর এই "ভার্বোসিটি ট্যাক্স" (Verbosity Tax) নিয়ে বিরক্ত লাগে, বিশেষ করে যখন প্রতিটি ফিল্ড ম্যানুয়ালি স্ক্যান করতে হয়। তবে pgx/v5 আসার পর কাজটা কিছুটা সহজ হয়েছে। জেনেরিক্স ব্যবহার করে ম্যানুয়াল স্ক্যানিংয়ের অংশটা এখন অনেক কম।

// pgx/v5 জেনেরিক্স ব্যবহার করে
p, err := pgx.CollectOneRow(rows, pgx.RowToAddrOfStructByName[Account])
if err != nil {
	return nil, err
}// pgx/v5 জেনেরিক্স ব্যবহার করে
p, err := pgx.CollectOneRow(rows, pgx.RowToAddrOfStructByName[Account])
if err != nil {
	return nil, err
}

ডোমেইন লেয়ার: বাফার হিসেবে ব্যবহার

internal/domain ফোল্ডারটাকে একটা বাফার হিসেবে দেখি যেখানে শুধু ইন্টারফেস আর স্ট্রাক্ট থাকে। কোনো ডাটাবেস ট্যাগ বা JSON ট্যাগ এখানে থাকা উচিত নয়। বিজনেজ লজিক ইমপ্লিমেন্ট করার সময় কখনও সরাসরি API বা স্টোর ইমপোর্ট করি না। টেস্টিংয়ের সময় রিয়েল ডাটাবেস ছাড়া মক (Mock) ডেটা দিয়ে টেস্ট করার এটাই সবচেয়ে সহজ উপায়।

যদি কোনো প্রোজেক্ট খোলার ৬০ সেকেন্ডের মধ্যে নির্দিষ্ট বিজনেস রুল বা কোয়েরি খুঁজে না পান, তবে আপনার Go Project Structure এ বড় কোনো গলদ আছে। আমি সবকিছু খোঁজার জন্য fzf ব্যবহার করলেও লেআউট এমনভাবে বানাই যাতে LSP আমাকে সঠিক জায়গায় ল্যান্ড করাতে পারে।

সহজ এবং ফ্ল্যাট স্ট্রাকচার সাধারণত দীর্ঘমেয়াদে জয়ী হয়। শুরুতে অনেক বেশি ফোল্ডার বা সফিস্টিকেটেড লেআউট নিলে পরবর্তীতে cognitive load অনেক বেড়ে যায়।

About the Author

Asaduzzaman Pavel

Software Engineer who actually enjoys the friction of well-architected systems. 15+ years building high-performance backends and infrastructure that handles real-world chaos at scale.

Open to new opportunities

Comments

  • Sign in with GitHub to comment
  • Keep it respectful and on-topic
  • No spam or self-promotion