paint-brush
ডিপেনডেন্সি ইনভার্সন প্রিন্সিপল ইন গো: এটি কী এবং কীভাবে এটি ব্যবহার করবেনদ্বারা@kirooha
27,528 পড়া
27,528 পড়া

ডিপেনডেন্সি ইনভার্সন প্রিন্সিপল ইন গো: এটি কী এবং কীভাবে এটি ব্যবহার করবেন

দ্বারা Kirill Parasotchenko10m2024/05/12
Read on Terminal Reader

অতিদীর্ঘ; পড়তে

এই নিবন্ধে, আমরা নির্ভরতা বিপরীত নীতি নিয়ে আলোচনা করব। সংক্ষেপে, আমরা এটি কী তা নিয়ে কথা বলব এবং উদাহরণ হিসাবে একটি সাধারণ Go অ্যাপ্লিকেশন ব্যবহার করে এই নীতিটি পরীক্ষা করব।
featured image - ডিপেনডেন্সি ইনভার্সন প্রিন্সিপল ইন গো: এটি কী এবং কীভাবে এটি ব্যবহার করবেন
Kirill Parasotchenko HackerNoon profile picture

ভূমিকা

এই নিবন্ধে, আমরা নির্ভরতা বিপরীত নীতি নিয়ে আলোচনা করব। সংক্ষেপে, আমরা এটি কী তা নিয়ে কথা বলব এবং উদাহরণ হিসাবে একটি সাধারণ Go অ্যাপ্লিকেশন ব্যবহার করে এই নীতিটি পরীক্ষা করব।

নির্ভরতা বিপরীত নীতি কি?

ডিপেনডেন্সি ইনভার্সন প্রিন্সিপল (DIP) হল অবজেক্ট-ওরিয়েন্টেড প্রোগ্রামিং (OOP) এর পাঁচটি সলিড নীতির মধ্যে একটি, রবার্ট সি. মার্টিন প্রথম প্রবর্তন করেছিলেন। এটি বলে:


  1. উচ্চ-স্তরের মডিউলগুলি নিম্ন-স্তরের মডিউলগুলি থেকে কিছু আমদানি করা উচিত নয়। উভয়েরই বিমূর্ততার উপর নির্ভর করা উচিত (যেমন, ইন্টারফেস)।


  2. বিমূর্ত বিবরণের উপর নির্ভর করা উচিত নয়। বিশদ বিবরণ (কংক্রিট বাস্তবায়ন) বিমূর্ততার উপর নির্ভর করা উচিত।


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

উদাহরণ

আসুন বিবেচনা করা যাক গো-তে DI নীতির বাস্তবায়ন কেমন হতে পারে। আমরা একটি একক এন্ডপয়েন্ট/বই সহ একটি HTTP অ্যাপ্লিকেশনের একটি সাধারণ উদাহরণ দিয়ে শুরু করব, যা একটি বইয়ের আইডির উপর ভিত্তি করে তথ্য প্রদান করে। বই সম্পর্কে তথ্য পুনরুদ্ধার করতে, অ্যাপ্লিকেশনটি একটি বহিরাগত HTTP পরিষেবার সাথে যোগাযোগ করবে।

প্রকল্পের কাঠামো

cmd - Go কমান্ড সহ ফোল্ডার। মূল ফাংশন এখানে থাকবে।


অভ্যন্তরীণ - অভ্যন্তরীণ অ্যাপ্লিকেশন কোড সহ ফোল্ডার। আমাদের সমস্ত কোড এখানে থাকবে।

DI ছাড়া স্প্যাগেটি কোডের উদাহরণ

main.go সহজভাবে HTTP সার্ভার শুরু করে।

 package main import ( "log" "net/http" "example.com/books/internal/app/httpbookapi" ) func main() { http.Handle("/book", &httpbookapi.Handler{}) log.Print("server listening at 9090") log.Fatal(http.ListenAndServe(":9090", nil)) }


আমাদের এইচটিটিপি এন্ডপয়েন্ট পরিচালনার জন্য এখানে কোড রয়েছে:

 package httpbookapi import ( "encoding/json" "fmt" "net/http" "example.com/books/internal/model" ) type Handler struct { } func (h *Handler) ServeHTTP(writer http.ResponseWriter, request *http.Request) { var ( ctx = request.Context() id = request.URL.Query().Get("id") book model.Book ) url := fmt.Sprintf("http://localhost:8080/book?id=%s", id) req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) if err != nil { http.Error(writer, err.Error(), http.StatusInternalServerError) return } resp, err := http.DefaultClient.Do(req) if err != nil { http.Error(writer, err.Error(), http.StatusInternalServerError) return } defer resp.Body.Close() if err := json.NewDecoder(resp.Body).Decode(&book); err != nil { http.Error(writer, err.Error(), http.StatusInternalServerError) return } book.Price = 10.12 if book.Title == "Pride and Prejudice" { book.Price += 2 } writer.Header().Add("Content-Type", "application/json") if err := json.NewEncoder(writer).Encode(book); err != nil { http.Error(writer, err.Error(), http.StatusInternalServerError) return } }

আপনি দেখতে পাচ্ছেন, বর্তমানে সমস্ত কোড সরাসরি হ্যান্ডলারের ভিতরে রয়েছে (বুকের মডেল বাদে)। হ্যান্ডলারে, আমরা একটি HTTP ক্লায়েন্ট তৈরি করি এবং একটি বহিরাগত পরিষেবার জন্য একটি অনুরোধ করি। তারপর আমরা বইয়ের কিছু মূল্য নির্ধারণ করি। এখানে, আমি বিশ্বাস করি যে কোনও বিকাশকারীর কাছে এটি স্পষ্ট যে এটি সর্বোত্তম ডিজাইন নয়, এবং বহিরাগত পরিষেবা কল করার জন্য হ্যান্ডলারের কাছ থেকে কোড বের করা দরকার। এটা করা যাক.

উন্নতির প্রথম ধাপ

প্রথম ধাপ হিসেবে, আসুন এই কোডটিকে একটি আলাদা জায়গায় নিয়ে যাই। এটি করার জন্য, আমরা অভ্যন্তরীণ/pkg/getbook/usecase.go নামে একটি ফাইল তৈরি করব, যেখানে আমাদের বই পুনরুদ্ধার এবং প্রক্রিয়াকরণের যুক্তি থাকবে এবং অভ্যন্তরীণ/pkg/getbook/types.go , যেখানে আমরা সংরক্ষণ করব প্রয়োজনীয় getbook প্রকার।


usecase.go কোড

 package getbook import ( "context" "encoding/json" "fmt" "net/http" ) type UseCase struct { bookServiceClient *http.Client } func NewUseCase() *UseCase { return &UseCase{} } func (u *UseCase) GetBook(ctx context.Context, id string) (*Book, error) { var ( book Book url = fmt.Sprintf("http://localhost:8080/book?id=%s", id) ) req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) if err != nil { return nil, err } resp, err := u.bookServiceClient.Do(req) if err != nil { return nil, err } defer resp.Body.Close() if err := json.NewDecoder(resp.Body).Decode(&book); err != nil { return nil, err } book.Price = 10.12 if book.Title == "Pride and Prejudice" { book.Price += 2 } return &book, nil }


type.go কোড

 package getbook type Book struct { ID string `json:"id"` Title string `json:"title"` Author string `json:"author"` Price float64 `json:"price"` }


নতুন হ্যান্ডলার কোড:

 package httpbookapi import ( "encoding/json" "net/http" "example.com/books/internal/pkg/getbook" ) type Handler struct { getBookUseCase *getbook.UseCase } func NewHandler(getBookUseCase *getbook.UseCase) *Handler { return &Handler{ getBookUseCase: getBookUseCase, } } func (h *Handler) ServeHTTP(writer http.ResponseWriter, request *http.Request) { var ( ctx = request.Context() id = request.URL.Query().Get("id") ) book, err := h.getBookUseCase.GetBook(ctx, id) if err != nil { http.Error(writer, err.Error(), http.StatusInternalServerError) return } writer.Header().Add("Content-Type", "application/json") if err := json.NewEncoder(writer).Encode(book); err != nil { http.Error(writer, err.Error(), http.StatusInternalServerError) return } }


আপনি দেখতে পাচ্ছেন, হ্যান্ডলার কোড অনেক বেশি পরিষ্কার হয়ে গেছে, কিন্তু এখন, getbook/usecase.go এ একবার নজর দেওয়া আমাদের জন্য অনেক বেশি আকর্ষণীয়

 type UseCase struct { bookServiceClient *http.Client }


UseCase এর *http.Client আকারে একটি নির্ভরতা রয়েছে, যা আমরা বর্তমানে কোনোভাবেই আরম্ভ করছি না। আমরা *http.Client-কে NewUseCase() কন্সট্রাক্টরের মধ্যে পাস করতে পারি অথবা সরাসরি কনস্ট্রাক্টরের মধ্যে *http.Client তৈরি করতে পারি। যাইহোক, আসুন আবার মনে করি DI নীতি আমাদের কী বলে।


উচ্চ-স্তরের মডিউলগুলি নিম্ন-স্তরের মডিউলগুলি থেকে কিছু আমদানি করা উচিত নয়। উভয়েরই বিমূর্ততার উপর নির্ভর করা উচিত (যেমন, ইন্টারফেস)


যাইহোক, এই পদ্ধতির সাথে, আমরা ঠিক বিপরীত কাজ করেছি। আমাদের উচ্চ-স্তরের মডিউল, গেটবুক, নিম্ন-স্তরের মডিউল, HTTP আমদানি করে।

ডিপেন্ডেন্সি ইনভার্সন প্রবর্তন

আসুন আমরা কীভাবে এটি ঠিক করতে পারি তা নিয়ে ভাবি। শুরু করতে, ইন্টারনাল/pkg/bookserviceclient/client.go নামে একটি ফাইল তৈরি করা যাক। এই ফাইলটিতে বাহ্যিক পরিষেবা এবং সংশ্লিষ্ট ইন্টারফেসে HTTP অনুরোধের বাস্তবায়ন থাকবে।

 package bookserviceclient import ( "context" "fmt" "io" "net/http" ) type Client interface { GetBook(ctx context.Context, id string) ([]byte, error) } type client struct { httpClient *http.Client } func NewClient() Client { return &client{ httpClient: http.DefaultClient, } } func (c *client) GetBook(ctx context.Context, id string) ([]byte, error) { var ( url = fmt.Sprintf("http://localhost:8080/book?id=%s", id) ) req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) if err != nil { return nil, err } resp, err := c.httpClient.Do(req) if err != nil { return nil, err } defer resp.Body.Close() b, err := io.ReadAll(resp.Body) if err != nil { return nil, err } return b, nil }


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

 package getbook import ( "context" "encoding/json" "example.com/books/internal/pkg/bookserviceclient" ) type UseCase struct { bookClient bookserviceclient.Client } func NewUseCase(bookClient bookserviceclient.Client) *Usecase { return &UseCase{ bookClient: bookClient, } } func (u *UseCase) GetBook(ctx context.Context, id string) (*Book, error) { var ( book Book ) b, err := u.bookClient.GetBook(ctx, id) if err != nil { return nil, err } if err := json.Unmarshal(b, &book); err != nil { return nil, err } book.Price = 10.12 if book.Title == "Pride and Prejudice" { book.Price += 2 } return &book, nil }


দেখে মনে হচ্ছে জিনিসগুলি উল্লেখযোগ্যভাবে উন্নত হয়েছে, এবং আমরা নিম্ন-স্তরের মডিউলে ব্যবহারের নির্ভরতা সমস্যাটি সমাধান করেছি। যাইহোক, এটি এখনও পুরোপুরি সেখানে নেই। এটাকে আরও এক ধাপ এগিয়ে নেওয়া যাক। এই মুহূর্তে, নির্ভরতা ঘোষণা করতে, useсase নিম্ন-স্তরের মডিউল থেকে একটি ইন্টারফেস ব্যবহার করছে। আমরা কি এই উন্নতি করতে পারি? যদি আমরা pkg/getbook/types.go- তে আমাদের প্রয়োজনীয় ইন্টারফেসগুলি ঘোষণা করি?


এইভাবে, আমরা নিম্ন-স্তরের মডিউলগুলির উপর স্পষ্ট নির্ভরতা সরিয়ে ফেলব। অর্থাৎ, আমাদের উচ্চ-স্তরের মডিউল তার ক্রিয়াকলাপের জন্য প্রয়োজনীয় সমস্ত ইন্টারফেস ঘোষণা করবে, এইভাবে, নিম্ন-স্তরের মডিউলগুলির উপর সমস্ত নির্ভরতা মুছে ফেলবে। অ্যাপ্লিকেশনের শীর্ষ স্তরে ( main.go ), আমরা তারপর ব্যবহার করার জন্য প্রয়োজনীয় সমস্ত ইন্টারফেস প্রয়োগ করব।


এছাড়াও, আসুন Go-তে রপ্তানিকৃত এবং অরপ্তানিকৃত প্রকারগুলি স্মরণ করি। আমাদের কি ইউসেস ইন্টারফেস রপ্তানি করতে হবে? এই ইন্টারফেসগুলি শুধুমাত্র এই প্যাকেজের অপারেশনের জন্য প্রয়োজনীয় নির্ভরতাগুলি নির্দিষ্ট করার জন্য প্রয়োজন, তাই সেগুলি রপ্তানি না করাই ভাল৷

চূড়ান্ত কোড

usecase.go

 package getbook import ( "context" "encoding/json" ) type UseCase struct { bookClient bookClient } func NewUseCase(bookClient bookClient) *UseCase { return &UseCase{ bookClient: bookClient, } } func (u *UseCase) GetBook(ctx context.Context, id string) (*Book, error) { var ( book Book ) b, err := u.bookClient.GetBook(ctx, id) if err != nil { return nil, err } if err := json.Unmarshal(b, &book); err != nil { return nil, err } book.Price = 10.12 if book.Title == "Pride and Prejudice" { book.Price += 2 } return &book, nil }


type.go

 package getbook import "context" type bookClient interface { GetBook(ctx context.Context, id string) ([]byte, error) } type Book struct { ID string `json:"id"` Title string `json:"title"` Author string `json:"author"` Price float64 `json:"price"` }


client.go

 package bookserviceclient import ( "context" "fmt" "io" "net/http" ) type Client struct { httpClient *http.Client } func NewClient() *Client { return &Client{ httpClient: http.DefaultClient, } } func (c *Client) GetBook(ctx context.Context, id string) ([]byte, error) { var ( url = fmt.Sprintf("http://localhost:8080/book?id=%s", id) ) req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) if err != nil { return nil, err } resp, err := c.httpClient.Do(req) if err != nil { return nil, err } defer resp.Body.Close() b, err := io.ReadAll(resp.Body) if err != nil { return nil, err } return b, nil }


main.go

 package main import ( "log" "net/http" "example.com/books/internal/app/httpbookapi" "example.com/books/internal/pkg/bookserviceclient" "example.com/books/internal/pkg/getbook" ) func main() { bookServiceClient := bookserviceclient.NewClient() useCase := getbook.NewUsecase(bookServiceClient) handler := httpbookapi.NewHandler(useCase) http.Handle("/book", handler) log.Print("server listening at 9090") log.Fatal(http.ListenAndServe(":9090", nil)) }

সারসংক্ষেপ

এই নিবন্ধে, আমরা Go-তে নির্ভরতা বিপরীত নীতিটি কীভাবে প্রয়োগ করতে হয় তা অনুসন্ধান করেছি। এই নীতিটি প্রয়োগ করা আপনার কোডকে স্প্যাগেটি হওয়া থেকে আটকাতে সাহায্য করতে পারে এবং এটি বজায় রাখা এবং পড়া সহজ করে তুলতে পারে। আপনার ক্লাসের নির্ভরতা বোঝা এবং কীভাবে সেগুলিকে সঠিকভাবে ঘোষণা করা যায় তা আপনার আবেদনকে আরও সমর্থন করার সময় আপনার জীবনকে ব্যাপকভাবে সহজ করতে পারে।