1. ent এর পরিচিতি

ent হলো একটি এন্টিটি ফ্রেমওয়ার্ক যা ফেসবুক দ্বারা গো ভাষার জন্য বিশেষ করে তৈরি করা। এটি বড় স্কেলের ডেটা মডেল অ্যাপ্লিকেশন নির্মাণ এবং অনুরক্ত রাখার প্রক্রিয়াটি সহজ করে। ent আমন্ত্রণে পালন করে প্রাথমিকভাবে নিম্নলিখিত সিদ্ধান্তগুলি:

  • ডাটাবেস স্কিমা বলে গ্রাফ গঠনে সহজভাবে মডেল করা যাবে।
  • স্কিমা গো ভাষার কোড ফর্মে সংজ্ঞায়িত করা।
  • কোড জেনারেশন ভিত্তিক স্থাবর প্রকার অনুসারে প্রয়োজনীয় ধরন সংজ্ঞান।
  • ডেটাবেস ক্যুয়ারি এবং গ্রাফ ট্রাভার্সালটি খুব সহজেই লিখার পাশাপাশি।
  • গো টেমপ্লেট ব্যবহার করে সহজেই এক্সটেন্ড এবং কাস্টমাইজ করা।

2. পরিবেশ সেটআপ

ent ফ্রেমওয়ার্ক ব্যবহার শুরু করতে, নিশ্চিত করুন যে আপনার ডেভেলপমেন্ট পরিবেশে Go ভাষা ইনস্টল করা আছে।

আপনার প্রজেক্ট ডায়রেক্টরি যদি GOPATH এর বাইরে বা আপনি GOPATH এর সাথে পরিচিত না হন, তবে নিম্নলিখিত কমান্ডটি ব্যবহার করে একটি নতুন Go মডিউল প্রজেক্ট তৈরি করার জন্য:

go mod init entdemo

এটি একটি নতুন Go মডিউল আইন্সট্যান্ট করে এবং আপনার entdemo প্রজেক্টের জন্য একটি নতুন go.mod ফাইল তৈরি করবে।

3. প্রথম স্কিমা সংজ্ঞানা

3.1. ent CLI ব্যবহার করে স্কিমা তৈরি

প্রথমে, আপনাকে আপনার প্রজেক্টের মূল ডায়রেক্টরিতে ent CLI টুলটি ব্যবহার করে নিম্নলিখিত কমান্ডটি চালাতে হবে যাতে স্কিমা নামক User তৈরি করা যায়:

go run -mod=mod entgo.io/ent/cmd/ent new User

উপরোক্ত কমান্ডটি entdemo/ent/schema/ ডিরেক্টরিতে ইউজার একটি স্কিমা জেনারেট করবে:

ফাইল entdemo/ent/schema/user.go:

package schema

import "entgo.io/ent"

// User holds the schema definition for the User entity.
type User struct {
    ent.Schema
}

// Fields of the User.
func (User) Fields() []ent.Field {
    return nil
}

// Edges of the User.
func (User) Edges() []ent.Edge {
    return nil
}

3.2. ফিল্ড যোগ করা

পরবর্তীতে, আমাদের দরকার ছোটাছুটি স্কিমা যোগ করা। নিচে ইউজার এন্টিটির দুইটি ফিল্ড যোগ করার একটি উদাহরণ দেওয়া হলে:

ফাইল পরিবর্তিত entdemo/ent/schema/user.go:

package schema

import (
    "entgo.io/ent"
    "entgo.io/ent/schema/field"
)

// Fields of the User.
func (User) Fields() []ent.Field {
    return []ent.Field{
        field.Int("age").
            Positive(),
        field.String("name").
            Default("unknown"),
    }
}

এই কোডটি ইউজার মডেলের দুইটি ফিল্ড সংজ্ঞা করে: উমার এবং নাম, যেখানে উমার ধনাত্মক পূর্ণসংখ্যা এবং নাম একটি স্ট্রিং যেখানে "অজানা" হলে এমনি ডিফল্ট মান।

3.3. ডাটাবেস এন্টিটি জেনারেট করা

স্কিমা সংজ্ঞায়িত করার পরে, আপনাকে go generate কমান্ডটি চালাতে হবে যাতে আপনি অন্যান্য ডাটাবেস যোগাযোগ লজিক তৈরি করতে পারেন।

আপনার প্রজেক্টের মূল ডায়রেক্টরিতে নিম্নলিখিত কমান্ডটি চালাতে হবে:

go generate ./ent

এই কমান্ডটি পূর্বে নির্ধারিত স্কিমা ভিত্তিক অনুরূপ গো কোড জেনারেট করবে, যা নিম্নলিখিত ফাইল স্ট্রাকচারে পরিণত হবে:

ent
├── client.go
├── config.go
├── context.go
├── ent.go
├── generate.go
├── mutation.go
... (সংক্ষেপের জন্য কিছু ফাইল অপসারিত আছে)
├── schema
│   └── user.go
├── tx.go
├── user
│   ├── user.go
│   └── where.go
├── user.go
├── user_create.go
├── user_delete.go
├── user_query.go
└── user_update.go

4.1. ডাটাবেস কানেকশন ইনস্টিলাইজেশন

মাইক্রোসফট ডাটাবেসে একটি কানেকশন স্থাপন করতে, আমরা এন্ট ফ্রেমওয়ার্ক দ্বারা প্রদানকৃত ওপেন ফাংশনটি ব্যবহার করতে পারি। প্রথমে, মাইক্রোসফট ড্রাইভার ইম্পোর্ট করুন এবং তারপর সঠিক কানেকশন স্ট্রিং প্রদান করে ডাটাবেস কানেকশনটি ইনিশিয়ালাইজ করুন।

package main

import (
    "context"
    "log"

    "entdemo/ent"
    
    _ "github.com/go-sql-driver/mysql" // মাইক্রোসফট ড্রাইভার ইম্পোর্ট
)

func main() {
    // মাইক্রোসফট ডাটাবেসের সাথে একটি সংযোগ স্থাপন করতে ent.Open ব্যবহার করুন।
    // নীচের "your_username", "your_password", এবং "your_database" স্থানধারণ প্লেসহোল্ডারগুলি পরিবর্তন করতে মনে রাখুন।
    client, err := ent.Open("mysql", "your_username:your_password@tcp(localhost:3306)/your_database?parseTime=True")
    if err != nil {
        log.Fatalf("mysql সাথে সংযোগ খোলার ব্যর্থ: %v", err)
    }
    defer client.Close()

    // স্বয়ংক্রিয়ভাবে মাইগ্রেশন টুল চালু করুন
    ctx := context.Background()
    if err := client.Schema.Create(ctx); err != nil {
        log.Fatalf("স্কিমা সম্পদ তৈরি করতে ব্যর্থ: %v", err)
    }
    
    // অতিরিক্ত ব্যবসা লজিক এখানে লিখা যেতে পারে
}

4.2. এন্টিটি তৈরি

একটি ব্যবহারকারী এন্টিটি তৈরি করা হলে, একটি নতুন এন্টিটি অবজেক্ট তৈরি করে এবং সেভ বা সেভএক্স মেথডের মাধ্যমে এটি ডাটাবেসে সংরক্ষণ করা হয়। নীচের কোডটি উদাহরণ করে দেখায় কীভাবে একটি নতুন ব্যবহারকারী এন্টিটি তৈরি এবং দুটি ফিল্ড বয়স এবং নাম এর মান সেট করা হয়।

// CreateUser ফাংশনটি ব্যবহার করা হয় নতুন ব্যবহারকারী এন্টিটি তৈরি করতে
func CreateUser(ctx context.Context, client *ent.Client) (*ent.User, error) {
    // client.User.Create() ব্যবহার করে একটি ব্যবহারকারী তৈরি করার জন্য অনুরোধ গড়ি,
    // তারপর মাল্টিপল সেট এবং সেটনাম মেথডগুলি চেইন করে এন্টিটি ফিল্ডগুলির মান সেট করা হয়।
    u, err := client.User.
        Create().
        SetAge(30).    // ব্যবহারকারীর বয়স সেট করুন
        SetName("a8m"). // ব্যবহারকারীর নাম সেট করুন
        Save(ctx)     // এন্টিটিটি ডাটাবেসে সংরক্ষণের জন্য Save কল করুন
    if err != nil {
        return nil, fmt.Errorf("ব্যবহারকারী তৈরি ব্যর্থ: %w", err)
    }
    log.Println("ব্যবহারকারী তৈরি হয়েছে: ", u)
    return u, nil
}

main ফাংশনে, আপনি CreateUser ফাংশনটি কল করে নতুন ব্যবহারকারী এন্টিটি তৈরি করতে পারেন।

func main() {
    // ...উল্লেখ করা ডাটাবেস কানেকশন স্থাপন কোড বাদ দিন

    // ব্যবহারকারী এন্টিটি তৈরি করুন
    u, err := CreateUser(ctx, client)
    if err != nil {
        log.Fatalf("ব্যবহারকারী তৈরি করতে ব্যর্থ: %v", err)
    }
    log.Printf("তৈরি ব্যবহারকারী: %#v\n", u)
}

4.3. এন্টিটি অনুসন্ধান

এন্টিটি অনুসন্ধান করতে, আমরা এন্ট দ্বারা জেনারেট করা অনুসন্ধান বিল্ডার ব্যবহার করতে পারি। নীচের কোডটি দেখায় কীভাবে "a8m" নামক ব্যবহারকারী অনুসন্ধান করা হয়।

// QueryUser ফাংশনটি ব্যবহার করা হয় নির্দিষ্ট নামের ব্যবহারকারী এন্টিটি অনুসন্ধান করতে
func QueryUser(ctx context.Context, client *ent.Client) (*ent.User, error) {
    // client.User.Query() ব্যবহার করে ব্যবহারকারী জন্য অনুসন্ধানের অনুরোধ গড়া,
    // তারপর Where মেথড চেইন করে অনুসন্ধান শর্তগুলি যোগ করুন, যেমন ব্যবহারকারীর নামের শর্তগুলি যোগ করুন
    u, err := client.User.
        Query().
        Where(user.NameEQ("a8m")).      // যেহেতু নাম "a8m"
        Only(ctx)                      // একটি শুধুমাত্র ফলাফল প্রত্যাশিত করা হয় Only মেথডের মাধ্যমে
    if err != nil {
        return nil, fmt.Errorf("ব্যবহারকারী অনুসন্ধান করা ব্যর্থ: %w", err)
    }
    log.Println("ব্যবহারকারী ফিরে এসেছে: ", u)
    return u, nil
}

main ফাংশনে, আপনি QueryUser ফাংশনটি কল করে ব্যবহারকারী এন্টিটি অনুসন্ধান করতে পারেন।

func main() {
    // ...উল্লেখ করা ডাটাবেস কানেকশন স্থাপন এবং ব্যবহারকারী তৈরি কোড বাদ দিন

    // ব্যবহারকারী এন্টিটি অনুসন্ধান করুন
    u, err := QueryUser(ctx, client)
    if err != nil {
        log.Fatalf("ব্যবহারকারী অনুসন্ধান করা ব্যর্থ: %v", err)
    }
    log.Printf("অনুসন্ধিত ব্যবহারকারী: %#v\n", u)
}

5.1. এজ এবং ইনভার্স এজ বোঝা

ent ফ্রেমওয়ার্কে, ডেটা মডেলটি একটি গ্রাফ স্ট্রাকচার হিসেবে ভিজ্যুয়ালাইজ করা হয়, যেখানে এন্টিটি গ্রাফের নোড প্রতিনিধিত্ব করে, এবং এন্টিটি মধ্যে সম্পর্কগুলি এজ দ্বারা প্রতিনিধিত্ব করা হয়। এজ হল একটি এন্টিটি থেকে অন্য এন্টিটির প্রতি সংযোগ, উদাহরণস্বরূপ, ব্যবহারকারী কিছু গাড়ি অধিকার করতে পারে।

ইনভার্স এজ হল পালাগে এজগুলির উল্টোদিকে রেফারেন্স, যা মানসিকভাবে এন্টিটিগুলির মধ্যে উল্টো সম্পর্ক প্রতিনিধিত্ব করে, কিন্তু ডেটাবেসে নতুন সম্পর্ক তৈরি করে না। উদাহরণস্বরূপ, একটি গাড়ির মাধ্যমে ইনভার্স এজ দিয়ে আমরা এই গাড়িটি কার্যরত করার ব্যবহারকারী পাওয়া যায়।

এজ এবং ইনভার্স এজের প্রধান অগ্রাধিকার হল সংযোগিত এন্টিটিগুলির মধ্যে ভাবমূর্তি এবং সরাসরি পথনির্ধারণ করা।

টিপ: ent এ, এজগুলি পারমানিক ডেটাবেস এর মধ্যের প্রথাগত ডেটাবেস বাইরাকী করে এবং টেবিলগুলি মধ্যে সম্পর্ক নির্ধারণ করতে ব্যবহৃত হয়।

5.2. স্কিমায় এজ শক্তি করা

প্রথমত, আমরা ent CLI ব্যবহার করে Car এবং Group এর জন্য আদি স্কিমা তৈরি করব:

go run -mod=mod entgo.io/ent/cmd/ent new Car Group

পরবর্তীতে, User স্কিমায়, আমরা Car সহ এজ সংজ্ঞায়িত করব যেখানে ব্যবহারকারী ও গাড়ির মধ্যে সম্পর্ক দেখাবে। আমরা ইহাতে cars নামের এজ যুক্ত করতে পারি ব্যবহারকারী এন্টিটিতে, যা বুঝায় যে একটি ব্যবহারকারীর অনেকগুলি গাড়ি থাকতে পারে।

// entdemo/ent/schema/user.go

// ব্যবহারকারী এর এজগুলি।
func (User) Edges() []ent.Edge {
    return []ent.Edge{
        edge.To("cars", Car.Type),
    }
}

এজ সংজ্ঞান করার পরে, আমাদের প্রসিক্রিয়া করতে হবে go generate ./ent আবার হালনাগাদ কোড জেনারেট করতে।

5.3. এজ ডেটা অপারেট করা

একটি ব্যবহারকারীর সাথে যুক্ত গাড়ি তৈরি করা সহজ প্রক্রিয়া। একটি ব্যবহারকারী এন্টিটি দেওয়া মাধ্যমে, আমরা নতুন গাড়ি এন্টিটি তৈরি করতে ব্যবহার করতে পারি এবং এটি ব্যবহারকারীর সাথে যুক্ত করতে পারি।

import (
    "context"
    "log"
    "entdemo/ent"
    // নিশ্চিত করুন যে, Car এর স্কিমা সংজ্ঞান করা হয়েছে
    _ "entdemo/ent/schema"
)

func CreateCarsForUser(ctx context.Context, client *ent.Client, userID int) error {
    user, err := client.User.Get(ctx, userID)
    if err != nil {
        log.Fatalf("ব্যবহারকারী পাওয়া যায়নি: %v", err)
        return err
    }

    // নতুন গাড়ি তৈরি করে এবং এটি ব্যবহারকারীর সাথে যুক্ত করা
    _, err = client.Car.
        Create().
        SetModel("টেসলা").
        SetRegisteredAt(time.Now()).
        SetOwner(user).
        Save(ctx)
    if err != nil {
        log.Fatalf("ব্যবহারকারীর জন্য গাড়ি তৈরি করার সাথে ব্যর্থ: %v", err)
        return err
    }

    log.Println("গাড়ি তৈরি করা হয়ছে এবং ব্যবহারকারীর সাথে সংযুক্ত করা হয়েছে")
    return nil
}

একইভাবে, একটি ব্যবহারকারীর গাড়ির জন্য ক্যুয়ারি করা সহজ। আমরা যদি চাই যে দিয়ে ব্যবহারকারী দ্বারা মন্যশয করা গাড়ির তালিকা পেতে, তবে আমরা নিম্নলিখিত করতে পারি:

func QueryUserCars(ctx context.Context, client *ent.Client, userID int) error {
    user, err := client.User.Get(ctx, userID)
    if err != nil {
        log.Fatalf("ব্যবহারকারী পাওয়া যায়নি: %v", err)
        return err
    }

    // ব্যবহারকারী দ্বারা মন্যশয করা সকল গাড়ি কুয়েরি করুন
    cars, err := user.QueryCars().All(ctx)
    if err != nil {
        log.Fatalf("গাড়ি কুয়েরি করার সাথে ব্যর্থ: %v", err)
        return err
    }

    for _, car := range cars {
        log.Printf("গাড়ি: %v, মডেল: %v", car.ID, car.Model)
    }
    return nil
}

উপরোক্ত পদক্ষেপগুলির মাধ্যমে, আমরা না শুধুমাত্র স্কিমা মধ্যে এজ সংজ্ঞান করার উপর অধিকার জিজ্ঞাসা করেছি, বরং এজের সাথে সম্পর্কিত তথ্য তৈরি এবং কুয়েরি করার প্রদর্শন করেছি।

6. গ্রাফ পথনির্ধারণ এবং কুয়েরি

6.1. গ্রাফ মন্ডলেস বোঝা

ent এ, গ্রাফ মন্ডলগুলি এন্টিটি এবং তাদের মধ্যস্থ এজের দ্বারা প্রতিপাদন করা হয়। প্রতিটি এন্টিটি গ্রাফের একই মান প্রতিনিধিত্ব করে, এবং এন্টিটি মধ্যের সম্পর্কগুলি এজ দ্বারা প্রতিনিধিত্ব করা হয়, যা এক-এক, এক-অনেক, অনেক-অনেক, ইত্যাদি হতে পারে। এই গ্রাফ মন্ডলে জটিল কুয়েরি এবং একটি সম্পর্কিত ডেটাবেস উপর প্রয়োজনীয়তা সহজ ও স্বত: সিদ্ধান্ত করা হয়।

6.2. গ্রাফ স্ট্রাকচার পার্সিং

গ্রাফ ট্রাভার্সাল কোড লিখতে বেশিরভাগ ক্ষেত্রে এজ মধ্যে ডাটা কে আসোসিয়েট করার মাধ্যমে ডাটা যোগাযো না হয় তাো ইউজারদের মধ্যে পাওয়া চাটারগুলি থাকাকে বুঝানো হয়। ent-এ গ্রাফ স্ট্রাকচার ট্রাভার্সাল করার একটি সহজ উদাহরণ নিম্নলিখিতটি আছে:

import (
    "context"
    "log"

    "entdemo/ent"
)

// GraphTraversal হল গ্রাফ স্ট্রাকচার ট্রাভার্সালের একটি উদাহরণ
func GraphTraversal(ctx context.Context, client *ent.Client) error {
    // "Ariel" নামের ইউজার অনুসন্ধান করুন
    a8m, err := client.User.Query().Where(user.NameEQ("Ariel")).Only(ctx)
    if err != nil {
        log.Fatalf("ব্যবহারকারী অনুসন্ধানে ব্যর্থ: %v", err)
        return err
    }

    // সব গাাড়ি যোগাযোর আরিয়েল এর তাো ট্রাভার্স করুন
    cars, err := a8m.QueryCars().All(ctx)
    if err != nil {
        log.Fatalf("গাাড়ি অনুসন্ধানে ব্যর্থ: %v", err)
        return err
    }
    for _, car := range cars {
        log.Printf("আরিয়েলের গাাড়ির মডেল: %s", car.Model)
    }

    // আরিয়েল যে সব গ্রুপে সদস্য তার ট্রাভার্স করুন
    groups, err := a8m.QueryGroups().All(ctx)
    if err != nil {
        log.Fatalf("গ্রুপ অনুসন্ধানে ব্যর্থ: %v", err)
        return err
    }
    for _, g := range groups {
        log.Printf("আরিয়েল একটি গুড় মেম্বার: %s", g.Name)
    }

    return nil
}

উপরের কোডটি গ্রাফ ট্রাভার্সালের একটি বেসিক উদাহরণ, যেখানে প্রথমে ইউজার ক্যাসোের অনুসন্ধান করা হয় এবং তারপর ইউজারের গাড়ি এবং গ্রুপগুলি ট্রাভার্স করা হয়।

7. ডাটাবেস স্কিমা ভিজুয়ালাইজেশন

7.1. অ্যাট্লাস সরঞ্জাম ইনস্টলেশন

ent দ্বারা জেনারেট করা ডাটাবেস স্কিমা ভিজুয়ালাইজ করার জন্য আমরা অ্যাটলাস সরঞ্জাম ব্যবহার করতে পারি। অ্যাটলাস ইনস্টলেশনের ধাপগুলি খুব সহজ। উদাহরণস্বরূপ, ম্যাকঅসে ইনস্টলেশন করতে আপনি brew ব্যবহার করতে পারেন:

brew install ariga/tap/atlas

নোট: অ্যাটলাস হল একটি সার্বজনীন ডাটাবেস মাইগ্রেশন সরঞ্জাম যা বিভিন্ন ডাটাবেসের জন্য টেবিল স্ট্রাকচার সংস্কার সংস্কার প্রবণতা কারণ করতে পারে। অ্যাটলাসের বিস্তারিত পরিচয়টি পরবর্তী অধ্যায়ে উল্লেখ করা হবে।

7.2. ERD এবং SQL স্কিমা জেনারেট

অ্যাটলাস ব্যবহার করে স্কিমা দেখার এবং এক্সপোর্ট করার জন্য খুব সহজ। অ্যাটলাস ইনস্টলেশনের পর আপনি নিম্নোক্ত প্রস্তাবিত কমান্ড ব্যবহার করে ERD দেখতে পারেন:

atlas schema inspect -d [ডাটাবেস_dsn] --format dot

অথবা সরাসরি SQL স্কিমা জেনারেট করতে পারেন:

atlas schema inspect -d [ডাটাবেস_dsn] --format sql

যেখানে [ডাটাবেস_dsn] আপনার ডাটা সুরক্ষা নাম (DSN) পয়েন্ট করে। উদাহরণস্বরূপ, SQLite ডাটাবেজের জন্য এরকম হতে পোরে:

atlas schema inspect -d "sqlite://file:ent.db?mode=memory&cache=shared" --format dot

এই কমান্ডগুলি দ্বারা জেনারেট করা আউটপুটগুলি পরবর্তীতে প্রদর্শন অথবা ডকুমেন্টে পরিণত করতে পারেন রিস্পেক্টিভ টুলগুলি ব্যবহার করে।

8. স্কিমা মাইগ্রেশন

8.1. স্বয়ংক্রিয় মাইগ্রেশন এবং ভার্সন মাইগ্রেশন

ent সহায়তা করে দুটি স্কিমা মাইগ্রেশন স্ট্রেটেজি সমর্থন করে: স্বয়ংক্রিয় মাইগ্রেশন এবং ভার্সন মাইগ্রেশন। স্বয়ংক্রিয় মাইগ্রেশন হল রানটাইমে স্কিমা পরিবর্তনগুলি পরীক্ষা এবং প্রয়াোগের প্রক্রিয়া, যা ডেভেলপমেন্ট এবং টেস্টিং এর জন্য উপযুক্ত। ভার্সন মাইগ্রেশন হল মাইগ্রেশন স্ক্রিপ্ট জেনারেট করা এবং প্রডাকশন দখল আগে যত্নশীল পর্যালোচনার এবং পরীক্ষার প্রয়োগ করা প্রক্রিয়া।

টিপ: স্বয়ংক্রিয় মাইগ্রেশনের জন্য, বিভাগ 4.1 এর বিষয়ে দেখুন।

8.2. ভার্সনযুক্ত মাইগ্রেশন গোাড়াো প্রক্রিয়া

ভার্সন মাইগ্রেশন প্রক্রিয়া হল মাইগ্রেশন ফাইল জেনারেট করা এবং অ্যাটলাস দ্বারা। নীচের প্রয়োজনীয় কমান্ডগুলি ব্যবহার করে মাইগ্রেশন ফাইল জেনারেট করা হয়:

atlas migrate diff -d ent/schema/path --dir migrations/dir

পরবর্তীতে, এই মাইগ্রেশন ফাইলগুলি ডাটাবেজে প্রয়াোগ করা যাোক:

atlas migrate apply -d migrations/dir --url database_dsn

এই প্রক্রিয়া অনুসরণ করে, আপনি ডাটাবেজ মাইগ্রেশনের ইতিহাস বজ্রপাত ধরে রাখতে পারেন এবং প্রত্যেক মাইগ্রেশনের প্রতি দখল আগে এবং পরীক্ষাৰ পরীক্ষা নিশ্চিত করতে পারেন।

টিপ: উদাহরণস্বরূপ সম্পর্কিত স্যাম্পল কোড এ https://github.com/ent/ent/tree/master/examples/start বাংলায় দেখুন।