পিএইচপিতে যথাযথ সংগ্রহস্থল প্যাটার্ন ডিজাইন?


291

উপস্থাপনা: আমি সম্পর্কিত ডেটাবেসগুলির সাথে এমভিসি আর্কিটেকচারে সংগ্রহস্থল প্যাটার্নটি ব্যবহার করার চেষ্টা করছি।

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

<?php

class DbUserRepository implements UserRepositoryInterface
{
    protected $db;

    public function __construct($db)
    {
        $this->db = $db;
    }

    public function findAll()
    {
    }

    public function findById($id)
    {
    }

    public function findByName($name)
    {
    }

    public function create($user)
    {
    }

    public function remove($user)
    {
    }

    public function update($user)
    {
    }
}

সমস্যা # 1: প্রচুর ক্ষেত্র

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

সংখ্যা # 2: অনেকগুলি পদ্ধতি methods

এই ক্লাসটি এখনই দুর্দান্ত দেখাচ্ছে, তবে আমি জানি যে একটি বাস্তব-বিশ্বের অ্যাপ্লিকেশনটিতে আমার আরও অনেক পদ্ধতি প্রয়োজন। উদাহরণ স্বরূপ:

  • findAllByNameAndStatus
  • findAllInCountry
  • findAllWithEmailAddressSet
  • findAllByAgeAndGender
  • findAllByAgeAndGenderOrderByAge
  • প্রভৃতি

আপনি দেখতে পাচ্ছেন, সম্ভাব্য পদ্ধতির একটি খুব, দীর্ঘ তালিকা থাকতে পারে। এবং তারপরে আপনি যদি উপরে ক্ষেত্রের নির্বাচনের ইস্যুতে যুক্ত করেন, সমস্যাটি আরও বেড়ে যায়। অতীতে আমি সাধারণত এই সমস্ত যুক্তি ঠিক আমার নিয়ামকটিতে রেখে দিতাম:

<?php

class MyController
{
    public function users()
    {
        $users = User::select('name, email, status')
            ->byCountry('Canada')->orderBy('name')->rows();

        return View::make('users', array('users' => $users));
    }
}

আমার সংগ্রহস্থলের পদ্ধতির সাথে, আমি এটি দিয়ে শেষ করতে চাই না:

<?php

class MyController
{
    public function users()
    {
        $users = $this->repo->get_first_name_last_name_email_username_status_by_country_order_by_name('Canada');

        return View::make('users', array('users' => $users))
    }

}

সমস্যা # 3: একটি ইন্টারফেসের সাথে মিল পাওয়া অসম্ভব

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

স্পেসিফিকেশন প্যাটার্ন?

এই বিশালাকার আমাকে বিশ্বাস যে সংগ্রহস্থলের শুধুমাত্র পদ্ধতি একটি নির্দিষ্ট নম্বর (মত থাকা উচিত save(), remove(), find(), findAll(), ইত্যাদি)। তবে তারপরে আমি কীভাবে সুনির্দিষ্ট লুকআপ চালাব? আমি স্পেসিফিকেশন প্যাটার্নটির কথা শুনেছি , তবে আমার কাছে মনে হয় এটি কেবলমাত্র রেকর্ডগুলির একটি সম্পূর্ণ সেট হ্রাস করে (মাধ্যমে IsSatisfiedBy()), যদি আপনি কোনও ডাটাবেস থেকে টানছেন তবে স্পষ্টতই প্রধান পারফরম্যান্সের সমস্যা রয়েছে।

সহায়তার প্রয়োজন?

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

উত্তর:


208

আমি ভেবেছিলাম আমার নিজের প্রশ্নের উত্তর দেওয়ার জন্য আমি ক্র্যাক করব। নিম্নলিখিতটি আমার আসল প্রশ্নে সমস্যাগুলি 1-3 এর সমাধান করার একমাত্র উপায়।

দাবি অস্বীকার: নিদর্শন বা কৌশল বর্ণনা করার সময় আমি সর্বদা সঠিক শব্দ ব্যবহার করতে পারি না। তার জন্য দুঃখিত।

লক্ষ্য সমূহ:

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

সমাধান

আমি আমার অবিরাম স্টোরেজ (ডাটাবেস) ইন্টারঅ্যাকশনটিকে দুটি বিভাগে বিভক্ত করছি: আর (পড়ুন) এবং সিইউডি (তৈরি করুন, আপডেট করুন, মুছুন)। আমার অভিজ্ঞতা হয়েছে যে পাঠাগুলি হ'ল যা কোনও অ্যাপ্লিকেশনকে ধীর করে দেয়। এবং ডেটা ম্যানিপুলেশন (সিইউডি) আসলে ধীর হলেও এটি ঘন ঘন ঘটে এবং তাই উদ্বেগের চেয়ে অনেক কম।

সিইউডি (তৈরি করুন, আপডেট করুন, মুছুন) সহজ। এটি প্রকৃত মডেলগুলির সাথে কাজ করা জড়িত হবে , যা পরে আমার Repositoriesকাছে অধ্যবসায়ের জন্য প্রেরণ করা হয় । দ্রষ্টব্য, আমার সংগ্রহস্থলগুলি এখনও একটি পঠন পদ্ধতি সরবরাহ করবে তবে কেবল অবজেক্ট তৈরির জন্য, প্রদর্শন নয়। আরও পরে।

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

কোড:

ব্যবহারকারী মডেল

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

class User
{
    public $id;
    public $first_name;
    public $last_name;
    public $gender;
    public $email;
    public $password;
}

সংগ্রহস্থল ইন্টারফেস

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

মনে রাখবেন যে আমার সংগ্রহস্থলগুলিতে প্রতিটিতে কেবল এই তিনটি পদ্ধতি থাকবে। save()পদ্ধতি উভয় তৈরি করে ব্যবহারকারিদের আপডেট, কেবল থাকুক বা না থাকুক ব্যবহারকারী বস্তুর একটি আইডি সেট আছে তার উপর নির্ভর করে জন্য দায়ী।

interface UserRepositoryInterface
{
    public function find($id);
    public function save(User $user);
    public function remove(User $user);
}

এসকিউএল সংগ্রহস্থল বাস্তবায়ন

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

class SQLUserRepository implements UserRepositoryInterface
{
    protected $db;

    public function __construct(Database $db)
    {
        $this->db = $db;
    }

    public function find($id)
    {
        // Find a record with the id = $id
        // from the 'users' table
        // and return it as a User object
        return $this->db->find($id, 'users', 'User');
    }

    public function save(User $user)
    {
        // Insert or update the $user
        // in the 'users' table
        $this->db->save($user, 'users');
    }

    public function remove(User $user)
    {
        // Remove the $user
        // from the 'users' table
        $this->db->remove($user, 'users');
    }
}

প্রশ্ন অবজেক্ট ইন্টারফেস

এখন আমাদের সংগ্রহশালা দ্বারা যত্ন নেওয়া CUD (তৈরি করুন, আপডেট করুন, মুছুন), আমরা আর (পড়ুন) এ ফোকাস করতে পারি । ক্যোয়ারী অবজেক্টস হ'ল কিছু ধরণের ডেটা লুকিং লজিকের একটি এনপ্যাপুলেশন। তারা নির্মাতাদের জিজ্ঞাসা করা হয় না । এটি আমাদের সংগ্রহস্থলের মতো বিমূর্ত করে আমরা এর বাস্তবায়নটি পরিবর্তন করতে এবং এটি আরও সহজ পরীক্ষা করতে পারি। ক্যোয়ারী অবজেক্টের উদাহরণ একটি AllUsersQueryবা AllActiveUsersQuery, বা এমনকি হতে পারে MostCommonUserFirstNames

আপনি ভাবতে পারেন "আমি কি আমার অনুসন্ধানাগুলিগুলিতে কেবল এই অনুসন্ধানগুলির জন্য পদ্ধতি তৈরি করতে পারি না?" হ্যাঁ, তবে এখানে কেন আমি এটি করছি না:

  • আমার সংগ্রহস্থলগুলি মডেল অবজেক্টগুলির সাথে কাজ করার জন্য। একটি আসল ওয়ার্ল্ড অ্যাপ্লিকেশনটিতে, আমি passwordযদি আমার সমস্ত ব্যবহারকারীদের তালিকা করতে চাই তবে আমাকে কেন ক্ষেত্রটি পাওয়ার দরকার হবে?
  • সংগ্রহশালা প্রায়শই মডেল নির্দিষ্ট, তবুও প্রায়শই একাধিক মডেল জড়িত। তাহলে আপনি আপনার পদ্ধতিটি কী ভাণ্ডারে রেখেছেন?
  • এটি আমার সংগ্রহশালাগুলিকে খুব সহজ রাখে — পদ্ধতিগুলির একটি বর্ধিত শ্রেণি নয়।
  • সমস্ত প্রশ্নগুলি এখন তাদের নিজস্ব শ্রেণিতে সংগঠিত organized
  • সত্যই, এই মুহুর্তে, কেবলমাত্র আমার ডাটাবেস স্তরটি বিমূর্ত করার জন্য সংগ্রহস্থলগুলি উপস্থিত রয়েছে।

আমার উদাহরণের জন্য আমি "AllUser" অনুসন্ধান করার জন্য একটি কোয়েরি অবজেক্ট তৈরি করব। ইন্টারফেসটি এখানে:

interface AllUsersQueryInterface
{
    public function fetch($fields);
}

প্রশ্নের অবজেক্ট বাস্তবায়ন

এই যেখানে আমরা আবার ডেটা ম্যাপার ব্যবহার করে বিকাশের গতি বাড়িয়ে তুলতে পারি। লক্ষ্য করুন যে আমি ফিরিয়ে নেওয়া ডেটাসেট — ক্ষেত্রগুলিতে একটি টুইট করতে দিচ্ছি। এটি প্রায় যতদূর আমি সম্পাদিত ক্যোয়ারী পরিচালনা করতে চাই। মনে রাখবেন, আমার ক্যোয়ারী অবজেক্টগুলি কোয়েরি বিল্ডার নয়। তারা কেবল একটি নির্দিষ্ট ক্যোয়ারি সম্পাদন করে। তবে, যেহেতু আমি জানি যে আমি সম্ভবত এটির একটি খুব ব্যবহার করব, বিভিন্ন পরিস্থিতিতে বিভিন্ন ক্ষেত্রে আমি নিজেকে ক্ষেত্র নির্দিষ্ট করার ক্ষমতা দিচ্ছি giving আমি কখনই আমার প্রয়োজনের ক্ষেত্রগুলি ফিরতে চাই না!

class AllUsersQuery implements AllUsersQueryInterface
{
    protected $db;

    public function __construct(Database $db)
    {
        $this->db = $db;
    }

    public function fetch($fields)
    {
        return $this->db->select($fields)->from('users')->orderBy('last_name, first_name')->rows();
    }
}

কন্ট্রোলারে যাওয়ার আগে, আমি এটি আরও শক্তিশালী উদাহরণের জন্য আরেকটি উদাহরণ দেখাতে চাই। হয়তো আমি একটি প্রতিবেদন ইঞ্জিন আছে এবং জন্য একটি প্রতিবেদন তৈরি করতে হবে AllOverdueAccounts। এটি আমার ডেটা ম্যাপারটির সাথে জটিল হতে পারে এবং আমি SQLএই পরিস্থিতিতে কিছু সত্য লিখতে চাই । কোনও সমস্যা নেই, এখানে এই ক্যোয়ারী অবজেক্টটি দেখতে কেমন:

class AllOverdueAccountsQuery implements AllOverdueAccountsQueryInterface
{
    protected $db;

    public function __construct(Database $db)
    {
        $this->db = $db;
    }

    public function fetch()
    {
        return $this->db->query($this->sql())->rows();
    }

    public function sql()
    {
        return "SELECT...";
    }
}

এটি এই প্রতিবেদনের জন্য আমার যুক্তিবাদকে সুন্দরভাবে এক শ্রেণিতে রাখে এবং এটি পরীক্ষা করা সহজ। আমি এটি আমার হৃদয়ের সামগ্রীতে উপহাস করতে পারি, বা এমনকি সম্পূর্ণরূপে একটি ভিন্ন বাস্তবায়ন ব্যবহার করতে পারি।

নিয়ামক

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

class UsersController
{
    public function index(AllUsersQueryInterface $query)
    {
        // Fetch user data
        $users = $query->fetch(['first_name', 'last_name', 'email']);

        // Return view
        return Response::view('all_users.php', ['users' => $users]);
    }

    public function add()
    {
        return Response::view('add_user.php');
    }

    public function insert(UserRepositoryInterface $repository)
    {
        // Create new user model
        $user = new User;
        $user->first_name = $_POST['first_name'];
        $user->last_name = $_POST['last_name'];
        $user->gender = $_POST['gender'];
        $user->email = $_POST['email'];

        // Save the new user
        $repository->save($user);

        // Return the id
        return Response::json(['id' => $user->id]);
    }

    public function view(SpecificUserQueryInterface $query, $id)
    {
        // Load user data
        if (!$user = $query->fetch($id, ['first_name', 'last_name', 'gender', 'email'])) {
            return Response::notFound();
        }

        // Return view
        return Response::view('view_user.php', ['user' => $user]);
    }

    public function edit(SpecificUserQueryInterface $query, $id)
    {
        // Load user data
        if (!$user = $query->fetch($id, ['first_name', 'last_name', 'gender', 'email'])) {
            return Response::notFound();
        }

        // Return view
        return Response::view('edit_user.php', ['user' => $user]);
    }

    public function update(UserRepositoryInterface $repository)
    {
        // Load user model
        if (!$user = $repository->find($id)) {
            return Response::notFound();
        }

        // Update the user
        $user->first_name = $_POST['first_name'];
        $user->last_name = $_POST['last_name'];
        $user->gender = $_POST['gender'];
        $user->email = $_POST['email'];

        // Save the user
        $repository->save($user);

        // Return success
        return true;
    }

    public function delete(UserRepositoryInterface $repository)
    {
        // Load user model
        if (!$user = $repository->find($id)) {
            return Response::notFound();
        }

        // Delete the user
        $repository->delete($user);

        // Return success
        return true;
    }
}

সর্বশেষ ভাবনা:

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

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

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

আমি বিকাশে সাহায্যের জন্য ডেটা ম্যাপার ব্যবহার করি, কারণ সাধারণ কাজগুলির জন্য পুনরাবৃত্তিশীল এসকিউএল লেখা কেবল হাস্যকর। তবে যেখানে প্রয়োজন সেখানে আপনি একেবারে এসকিউএল লিখতে পারেন (জটিল প্রশ্ন, রিপোর্টিং ইত্যাদি)। এবং আপনি যখন করেন, এটি একটি সুন্দরভাবে নামযুক্ত ক্লাসে ছড়িয়ে যায়।

আমি আমার পদ্ধতির আপনার গ্রহণ শুনে ভাল লাগবে!


জুলাই 2015 আপডেট:

আমি এই সব দিয়ে শেষ যেখানে মন্তব্য জিজ্ঞাসা করা হয়েছে। ঠিক আছে, আসলে এটি খুব দূরে নয়। সত্য সত্য, আমি এখনও সত্যিই সংগ্রহস্থল পছন্দ করি না। আমি এগুলিকে বেসিক লুপআপের জন্য ওভারকিল খুঁজে পাই (বিশেষত যদি আপনি ইতিমধ্যে একটি ORM ব্যবহার করছেন), এবং আরও জটিল প্রশ্নগুলির সাথে কাজ করার সময় অগোছালো।

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


4
@ পেহায়া আবার, উদাহরণগুলি সহজ রাখা ছিল। কোডের টুকরোটিকে উদাহরণের বাইরে রেখে দেওয়া খুব সাধারণ বিষয় যদি তারা বিষয়টিতে বিশেষভাবে হাতে না নেয় tain বাস্তবে, আমি আমার নির্ভরতা মধ্যে পাস হবে।
জোনাথন

4
আপনি আপনার তৈরি থেকে তৈরি করুন, আপডেট করুন এবং মুছুন এইটিকে আকর্ষণীয়। ভেবেছিলেন এটি কমান্ড ক্যোয়ারির দায়িত্বশীলতা পৃথককরণ (সিকিউআরএস) এর উল্লেখযোগ্য হবে যা আনুষ্ঠানিকভাবে কেবল এটি করে। martinfowler.com/bliki/CQRS.html
আদম

2
@ জনাথন আপনার নিজের প্রশ্নের উত্তর দিয়ে দেড় বছর হয়ে গেছে। আমি ভাবছিলাম যে আপনি এখনও নিজের উত্তর নিয়ে খুশি এবং যদি এখন আপনার বেশিরভাগ প্রকল্পের জন্য এটিই আপনার মূল সমাধান? গত কয়েক সপ্তাহ ধরে আমি সংগ্রহস্থলগুলিতে বরাদ্দ পড়ছি এবং আমি দেখেছি যে কীভাবে এটি প্রয়োগ করা উচিত তার জন্য আলোট লোকের নিজস্ব ব্যাখ্যা রয়েছে। আপনার এটিকে ফোন করে জিজ্ঞাসাগুলির প্রশ্ন রয়েছে, তবে এটি কি বিদ্যমান প্যাটার্নটি সঠিক? আমি মনে করি এটি অন্যান্য ভাষায় ব্যবহৃত হতে দেখেছি।
বোয়ডি

1
@ জোনাথন: আপনি কীভাবে এমন প্রশ্নগুলি পরিচালনা করবেন যেগুলি কোনও ব্যবহারকারীকে "আইডি" নয় বরং উদাহরণস্বরূপ "ইউজারনেম" বা একাধিক শর্তযুক্ত আরও জটিল প্রশ্নগুলি দ্বারা ফিন করা উচিত?
গিজমো

1
@ জিজমো ক্যোয়ারী অবজেক্টগুলি ব্যবহার করে, আপনার আরও জটিল প্রশ্নের সাথে সহায়তা করতে আপনি অতিরিক্ত পরামিতিগুলিও পাস করতে পারেন। উদাহরণস্বরূপ, যদি আপনি কন্সট্রাকটর এটি করতে পারেন: new Query\ComplexUserLookup($username, $anotherCondition)। অথবা, সেটার পদ্ধতিগুলির মাধ্যমে এটি করুন $query->setUsername($username);। আপনি এটি সত্যই ডিজাইন করতে পারেন তবে এটি আপনার নির্দিষ্ট অ্যাপ্লিকেশনটির জন্য অর্থবোধ করে এবং আমার কাছে মনে হয় যে ক্যোয়ারী অবজেক্টগুলি এখানে প্রচুর নমনীয়তা ফেলেছে।
জোনাথন

48

আমার অভিজ্ঞতার ভিত্তিতে, এখানে আপনার প্রশ্নের কয়েকটি উত্তর রয়েছে:

প্রশ্ন: আমাদের প্রয়োজনীয় ক্ষেত্রগুলি ফিরিয়ে আনতে আমরা কীভাবে মোকাবিলা করব?

উত্তর: আমার অভিজ্ঞতা থেকে এটি অ্যাড-হক সম্পর্কিত বনাম সম্পূর্ণ সত্তাগুলির সাথে ডিল করার জন্য সত্যই উত্সাহিত হয়।

একটি সম্পূর্ণ সত্তা একটি Userবস্তুর মত কিছু । এর বৈশিষ্ট্য এবং পদ্ধতি ইত্যাদি রয়েছে এটি আপনার কোডবেজে প্রথম শ্রেণির নাগরিক।

একটি অ্যাডহক ক্যোয়ারী কিছু ডেটা ফেরত দেয় তবে আমরা এর বাইরে কিছু জানি না। অ্যাপ্লিকেশনটির চারপাশে ডেটা পাস করার সাথে সাথে প্রসঙ্গ ছাড়াই এটি করা হয়। এটা কি User? একজন Userকিছু Orderতথ্য সংযুক্ত? আমরা আসলে জানি না।

আমি পূর্ণ সত্তা সঙ্গে কাজ পছন্দ।

আপনি ঠিক বলেছেন যে আপনি প্রায়শই এমন ডেটা ফিরিয়ে আনবেন যা আপনি ব্যবহার করবেন না, তবে আপনি এটি বিভিন্ন উপায়ে সম্বোধন করতে পারেন:

  1. আগ্রাসনীয়ভাবে সত্তাগুলি ক্যাশে করুন যাতে আপনি কেবলমাত্র ডাটাবেস থেকে একবারে পঠন মূল্যের মূল্য প্রদান করেন।
  2. আপনার সত্তাগুলির মডেলিংয়ে আরও সময় ব্যয় করুন যাতে তাদের মধ্যে ভাল পার্থক্য থাকে। (একটি বৃহত সত্তাকে দুটি ছোট ছোট সত্তা ইত্যাদিতে বিভক্ত করার বিষয়টি বিবেচনা করুন)
  3. সত্ত্বার একাধিক সংস্করণ থাকার কথা বিবেচনা করুন। আপনি একটি থাকতে পারে Userফিরে শেষ জন্য এবং হয়ত একটি UserSmallAJAX এর কলের জন্য। একটিতে 10 টি সম্পত্তি এবং একটিতে 3 টি বৈশিষ্ট্য থাকতে পারে।

অ্যাড-হক প্রশ্নগুলির সাথে কাজ করার ডাউনসাইডস:

  1. আপনি অনেক প্রশ্নের জুড়ে মূলত একই ডেটা দিয়ে শেষ করেন। উদাহরণস্বরূপ, একটি দিয়ে Userআপনি select *অনেকগুলি কলের জন্য মূলত একই লেখা শেষ করবেন । একটি কল 10 টির মধ্যে 8 টি ক্ষেত্র পাবে, একজনের 10 টির মধ্যে 5 টি, একজনের মধ্যে 10 টির মধ্যে 7 টি আসবে Why 10 এর মধ্যে 10 পেয়ে একটি কল দিয়ে সমস্ত প্রতিস্থাপন করবেন না কেন? এটি খারাপ হওয়ার কারণটি হ'ল পুনরায় ফ্যাক্টর / পরীক্ষা / উপহাস করা খুন।
  2. সময়ের সাথে সাথে আপনার কোড সম্পর্কে উচ্চ স্তরে যুক্তি দেওয়া খুব শক্ত হয়ে যায়। " Userএত ধীর কেন ?" এর মতো বক্তব্যের বদলে ? আপনি এক-অফ ক্যোয়ারী ট্র্যাকিং শেষ করেন এবং তাই বাগ সংশোধনগুলি ছোট এবং স্থানীয়করণের প্রবণতা।
  3. অন্তর্নিহিত প্রযুক্তিটি প্রতিস্থাপন করা সত্যই কঠিন। আপনি যদি এখনই মাইএসকিউএল-এ সমস্ত কিছু সংরক্ষণ করেন এবং মঙ্গোডিবিতে যেতে চান, তবে এটি মুষ্টিমেয় সত্তার চেয়ে 100 অ্যাড-হক কলগুলি প্রতিস্থাপন করা অনেক শক্ত hard

প্রশ্ন: আমার সংগ্রহশালায় আমার অনেকগুলি পদ্ধতি থাকবে।

উত্তর: একত্রীকরণ কলগুলি বাদে আমি এর বাইরে আর কোনও উপায় দেখিনি। পদ্ধতিটি আপনার আবেদনের কলগুলিতে আপনার অ্যাপ্লিকেশনটির বৈশিষ্ট্যগুলিতে সত্যই ম্যাপ। আরও বৈশিষ্ট্য, তত বেশি ডেটা নির্দিষ্ট কল। আপনি বৈশিষ্ট্যগুলিকে পিছনে ঠেলাতে এবং একই কলগুলিকে একটিতে মার্জ করার চেষ্টা করতে পারেন।

দিনের শেষে জটিলতা কোথাও কোথাও উপস্থিত থাকতে হবে। একটি সংগ্রহস্থল প্যাটার্নের সাহায্যে আমরা এটিকে স্টোর প্রক্রিয়াগুলির একগুচ্ছ তৈরির পরিবর্তে সংগ্রহস্থল ইন্টারফেসে ঠেলে দিয়েছি।

কখনও কখনও আমাকে নিজেকে বলতে হয়, "আচ্ছা এটি অন্য কোথাও দিতে হয়েছিল! কোনও রূপালী বুলেট নেই।"


খুব পূর্ণ উত্তরের জন্য ধন্যবাদ। আপনি আমাকে এখন চিন্তা করতে পেয়েছেন। এখানে আমার বড় উদ্বেগ হ'ল আমি যা কিছু পড়ি তা হ'ল না SELECT *, কেবল আপনার প্রয়োজনীয় ক্ষেত্রগুলি নির্বাচন করুন। উদাহরণস্বরূপ, এই প্রশ্নটি দেখুন । আপনি যে সমস্ত অ্যাড-হকের প্রশ্নের বিষয়ে কথা বলছেন, আমি অবশ্যই বুঝতে পেরেছি আপনি কোথা থেকে এসেছেন। আমার এখনই একটি খুব বড় অ্যাপ্লিকেশন রয়েছে যার মধ্যে অনেকগুলি রয়েছে। এটাই আমার "আচ্ছা কোথাও দিতে হবে!" মুহূর্তে, আমি সর্বাধিক পারফরম্যান্সের জন্য বেছে নিয়েছি। যাইহোক, এখন আমি বিভিন্ন প্রশ্নের অনেকগুলি নিয়ে কাজ করছি।
জোনাথন

1
এক অনুসরণ চিন্তা। আমি একটি আর — সিইউডি পদ্ধতির ব্যবহার করার জন্য একটি প্রস্তাবনা দেখেছি। যেহেতু readsপ্রায়শই পারফরম্যান্স সংক্রান্ত সমস্যা দেখা দেয় তাই আপনি তাদের জন্য আরও কাস্টম ক্যোয়ারী পদ্ধতির ব্যবহার করতে পারেন, যা প্রকৃত ব্যবসায়ের সামগ্রীতে অনুবাদ করে না। তারপরে এবং এর জন্য create, একটি ওআরএম ব্যবহার করুন, যা পুরো অবজেক্টের সাথে কাজ করে। এই পদ্ধতির কোনও চিন্তা? updatedelete
জোনাথন

1
"নির্বাচন করুন" ব্যবহার করার জন্য একটি নোট হিসাবে। আমি অতীতে এটি করেছি এবং এটি ঠিক কাজ করেছে - যতক্ষণ না আমরা ভারচর (সর্বোচ্চ) ক্ষেত্রগুলিতে আঘাত করি না hit যারা আমাদের জিজ্ঞাসা হত্যা করেছে। সুতরাং আপনার যদি কালি, ছোট টেক্সট ক্ষেত্র ইত্যাদি সহ টেবিলগুলি থাকে তবে এটি খুব খারাপ নয়। অপ্রাকৃত মনে হলেও সফ্টওয়্যারটি সেভাবে চলে। যা খারাপ ছিল তা হঠাৎ ভাল এবং বিপরীতে।
ryan1234

1
আর-সিইউডি পন্থাটি আসলে সিকিউআরএস
মাইকএসডাব্লু

2
@ ryan1234 "দিনের শেষে জটিলতাটির কোথাও কোথাও অস্তিত্ব রয়েছে" " এই জন্য আপনাকে ধন্যবাদ. আমাকে আরও ভাল বোধ করে।
জননি

20

আমি নিম্নলিখিত ইন্টারফেস ব্যবহার:

  • Repository - লোড, সন্নিবেশ, আপডেট এবং সত্তা সরিয়ে দেয়
  • Selector - ফিল্টারগুলির উপর ভিত্তি করে সত্তা সন্ধান করে একটি সংগ্রহস্থলে
  • Filter - ফিল্টারিং লজিক encapsulates

আমার Repositoryহ'ল ডেটাবেস অজ্ঞেয়; আসলে এটি কোনও অধ্যবসায় নির্দিষ্ট করে না; এটি যে কোনও কিছু হতে পারে: এসকিউএল ডাটাবেস, এক্সএমএল ফাইল, দূরবর্তী পরিষেবা, বাহ্যিক স্থান থেকে একটি এলিয়েন ইত্যাদি অনুসন্ধান সক্ষমতা অনুসন্ধানের জন্য, ফিল্টার, -ড, বাছাই এবং গণনা করা যেতে পারে এমন Repositoryএকটি গঠন করে । শেষ পর্যন্ত, নির্বাচক অধ্যবসায় থেকে এক বা একাধিক আনেন ।SelectorLIMITEntities

এখানে কিছু নমুনা কোড রয়েছে:

<?php
interface Repository
{
    public function addEntity(Entity $entity);

    public function updateEntity(Entity $entity);

    public function removeEntity(Entity $entity);

    /**
     * @return Entity
     */
    public function loadEntity($entityId);

    public function factoryEntitySelector():Selector
}


interface Selector extends \Countable
{
    public function count();

    /**
     * @return Entity[]
     */
    public function fetchEntities();

    /**
     * @return Entity
     */
    public function fetchEntity();
    public function limit(...$limit);
    public function filter(Filter $filter);
    public function orderBy($column, $ascending = true);
    public function removeFilter($filterName);
}

interface Filter
{
    public function getFilterName();
}

তারপরে, একটি বাস্তবায়ন:

class SqlEntityRepository
{
    ...
    public function factoryEntitySelector()
    {
        return new SqlSelector($this);
    }
    ...
}

class SqlSelector implements Selector
{
    ...
    private function adaptFilter(Filter $filter):SqlQueryFilter
    {
         return (new SqlSelectorFilterAdapter())->adaptFilter($filter);
    }
    ...
}
class SqlSelectorFilterAdapter
{
    public function adaptFilter(Filter $filter):SqlQueryFilter
    {
        $concreteClass = (new StringRebaser(
            'Filter\\', 'SqlQueryFilter\\'))
            ->rebase(get_class($filter));

        return new $concreteClass($filter);
    }
}

ধারণাটি হ'ল জেনেরিক Selectorব্যবহার করে Filterতবে বাস্তবায়ন SqlSelectorব্যবহার করে SqlFilter; SqlSelectorFilterAdapterএকটি জেনেরিক আত্তীকরণ Filterএকটি কংক্রিট করতে SqlFilter

ক্লায়েন্ট কোড Filterবস্তু তৈরি করে (যা জেনেরিক ফিল্টার) তবে নির্বাচকের কংক্রিট প্রয়োগে সেই ফিল্টারগুলি এসকিউএল ফিল্টারগুলিতে রূপান্তরিত হয়।

অন্যান্য নির্বাচক বাস্তবায়নের মত InMemorySelector, থেকে রুপান্তর Filterকরতে InMemoryFilterতাদের নির্দিষ্ট ব্যবহার InMemorySelectorFilterAdapter; সুতরাং, প্রতিটি নির্বাচক বাস্তবায়ন তার নিজস্ব ফিল্টার অ্যাডাপ্টারের সাথে আসে।

এই কৌশলটি ব্যবহার করে আমার ক্লায়েন্ট কোড (বসিন স্তরগুলিতে) কোনও নির্দিষ্ট সংগ্রহস্থল বা নির্বাচক প্রয়োগের বিষয়ে চিন্তা করে না।

/** @var Repository $repository*/
$selector = $repository->factoryEntitySelector();
$selector->filter(new AttributeEquals('activated', 1))->limit(2)->orderBy('username');
$activatedUserCount = $selector->count(); // evaluates to 100, ignores the limit()
$activatedUsers = $selector->fetchEntities();

পিএস এটি আমার বাস্তব কোডটির একটি সরলীকরণ


"সংগ্রহশালা - লোড, সন্নিবেশ, আপডেট এবং সত্তা সরিয়ে দেয়" এটি একটি "পরিষেবা স্তর", "ডিএও", "বিএলএল" কি করতে পারে
ইউশা আলেয়াউব

5

আমি বর্তমানে এগুলি নিজের মধ্যে উপলব্ধি করার চেষ্টা করছি বলে আমি এটিতে কিছুটা যুক্ত করব।

# 1 এবং 2

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

class DbUserRepository implements UserRepositoryInterface
{
    public function findAll()
    {
        return User::all();
    }

    public function get(Array $columns)
    {
       return User::select($columns);
    }

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

তবে আপনি যদি কোনও ওআরএম এড়াতে চান তবে আপনি যা খুঁজছেন তা পেতে আপনাকে "নিজের নিজস্ব রোল" করতে হবে।

# 3

ইন্টারফেসগুলি কঠোর এবং দ্রুত প্রয়োজনীয়তা হিসাবে ধরা হয় না। কিছু ইন্টারফেস বাস্তবায়ন করতে এবং এতে যুক্ত করতে পারে। এটি যা করতে পারে না তা সেই ইন্টারফেসের প্রয়োজনীয় ফাংশনটি প্রয়োগ করতে ব্যর্থ। জিনিসগুলি DRY রাখতে আপনি ক্লাসের মতো ইন্টারফেসও প্রসারিত করতে পারেন।

এটি বলেছিল, আমি কেবল একটি উপলব্ধি পেতে শুরু করছি তবে এই উপলব্ধি আমাকে সহায়তা করেছে।


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

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

3

আমরা (আমার সংস্থায়) এটির সাথে যেভাবে আচরণ করি সে সম্পর্কে আমি কেবল মন্তব্য করতে পারি। প্রথমত পারফরম্যান্স আমাদের পক্ষে খুব বেশি সমস্যা নয়, তবে পরিষ্কার / সঠিক কোড থাকাটাই।

সবার আগে আমরা মডেলগুলি সংজ্ঞায়িত করি যেমন একটি UserModelযা কোনও UserEntityবস্তু তৈরি করতে একটি ORM ব্যবহার করে। যখন কোনও UserEntityমডেল থেকে লোড হয় সমস্ত ক্ষেত্র লোড হয় are বিদেশী সত্ত্বাগুলি রেফারেন্সের ক্ষেত্রগুলির জন্য আমরা স্বতন্ত্র প্রতিষ্ঠানগুলি তৈরি করতে উপযুক্ত বিদেশী মডেলটি ব্যবহার করি। এই সত্তাগুলির জন্য ডেটা অনডেম্যান্ড লোড হবে। এখন আপনার প্রাথমিক প্রতিক্রিয়া হতে পারে ... ??? ... !!! আমি আপনাকে একটি উদাহরণ একটি সামান্য উদাহরণ দিতে দিন:

class UserEntity extends PersistentEntity
{
    public function getOrders()
    {
        $this->getField('orders'); //OrderModel creates OrderEntities with only the ID's set
    }
}

class UserModel {
    protected $orm;

    public function findUsers(IGetOptions $options = null)
    {
        return $orm->getAllEntities(/*...*/); // Orm creates a list of UserEntities
    }
}

class OrderEntity extends PersistentEntity {} // user your imagination
class OrderModel
{
    public function findOrdersById(array $ids, IGetOptions $options = null)
    {
        //...
    }
}

আমাদের ক্ষেত্রে $dbএকটি ওআরএম যা সত্তা লোড করতে সক্ষম। মডেলটি ওআরএমকে নির্দিষ্ট ধরণের সত্তার একটি সেট লোড করার নির্দেশ দেয়। ওআরএম একটি ম্যাপিং ধারণ করে এবং সত্তায় সেই সত্তার জন্য সমস্ত ক্ষেত্র ইনজেক্ট করতে ব্যবহার করে। বিদেশী ক্ষেত্রগুলির জন্য তবে কেবলমাত্র সেই আইডিগুলির মধ্যে লোড করা হয় are এই ক্ষেত্রে রেফারেন্সযুক্ত অর্ডারের কেবল আইডির সাহায্যে OrderModelসৃজনগুলি তৈরি করে OrderEntity। যখন PersistentEntity::getFieldডাকা পরার OrderEntityসত্তা মধ্যে সব ক্ষেত্র অলস বুট করার জন্য প্রয়োজনীয় এর মডেল নির্দেশ OrderEntityগুলি। OrderEntityএকটি ব্যবহারকারীর সাথে যুক্ত সমস্ত গুলি একটি ফলাফল-সেট হিসাবে বিবেচিত হবে এবং একবারে লোড হবে।

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

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

$objOptions->getConditionHolder()->addConditionBind(
    new ConditionBind(
        new Condition('orderProduct.product', ICondition::OPERATOR_IS, $argObjProduct)
    )
);

এই সিস্টেমের একটি সহজতম সংস্করণ হ'ল ক্যোয়ারির WHERE অংশটি সরাসরি মডেলটিতে স্ট্রিং হিসাবে পাস করা।

এই বেশ জটিল প্রতিক্রিয়ার জন্য আমি দুঃখিত। আমি যত দ্রুত সম্ভব আমাদের কাঠামোটি সংক্ষিপ্ত করার চেষ্টা করেছি। আপনার যদি কোনও অতিরিক্ত প্রশ্ন থাকে তবে নির্দ্বিধায় তাদের জিজ্ঞাসা করুন এবং আমি আমার উত্তর আপডেট করব।

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


3

এগুলি আমি দেখেছি কিছু ভিন্ন সমাধান। তাদের প্রত্যেকের পক্ষে মতামত রয়েছে, তবে সিদ্ধান্ত নেওয়া আপনার পক্ষে।

সমস্যা # 1: প্রচুর ক্ষেত্র

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

public function findColumnsById($id, array $columns = array()){
    if (empty($columns)) {
        // use *
    }
}

public function findById($id) {
    $data = $this->findColumnsById($id);
}

সংখ্যা # 2: অনেকগুলি পদ্ধতি methods

আমি এক বছর আগে প্রোপেল ওআরএম এর সাথে সংক্ষিপ্তভাবে কাজ করেছি এবং এটি সেই অভিজ্ঞতা থেকে আমি কী মনে করতে পারি তা ভিত্তিক। বিদ্যমান ডেটাবেস স্কিমার উপর ভিত্তি করে প্রোপেলের কাছে শ্রেণিক কাঠামো উত্পন্ন করার বিকল্প রয়েছে। এটি প্রতিটি টেবিলের জন্য দুটি বস্তু তৈরি করে। প্রথম অবজেক্টটি আপনি বর্তমানে তালিকাভুক্ত যা অনুরূপ অ্যাক্সেস ফাংশন একটি দীর্ঘ তালিকা; findByAttribute($attribute_value)। পরবর্তী বস্তুটি এই প্রথম বস্তু থেকে উত্তরাধিকার সূত্রে প্রাপ্ত। আপনার আরও জটিল গেটর ফাংশনগুলি তৈরি করতে আপনি এই সন্তানের অবজেক্টটি আপডেট করতে পারেন।

আর একটি সমাধান __call()হ'ল অ্যাকশনযোগ্য কিছুতে নির্ধারিত ফাংশনগুলিকে মানচিত্র করতে ব্যবহার করা হবে । আপনার __callপদ্ধতিটি ফাইন্ডবাইআইডি এবং ফাইন্ডবাইনেমকে বিভিন্ন ক্যোয়ারিতে পার্স করতে সক্ষম হবে।

public function __call($function, $arguments) {
    if (strpos($function, 'findBy') === 0) {
        $parameter = substr($function, 6, strlen($function));
        // SELECT * FROM $this->table_name WHERE $parameter = $arguments[0]
    }
}

আমি আশা করি এটি কিছুটা হলেও সহায়তা করে।



0

আমি @ ryan1234 এর সাথে একমত হয়েছি যে আপনার কোডের মধ্যে সম্পূর্ণ বস্তুর চারপাশে পাস করা উচিত এবং এই বিষয়গুলি পেতে জেনেরিক ক্যোয়ারী পদ্ধতি ব্যবহার করা উচিত।

Model::where(['attr1' => 'val1'])->get();

বাহ্যিক / শেষ পয়েন্ট ব্যবহারের জন্য আমি সত্যিই গ্রাফকিউএল পদ্ধতিটি পছন্দ করি।

POST /api/graphql
{
    query: {
        Model(attr1: 'val1') {
            attr2
            attr3
        }
    }
}

0

সমস্যা # 3: একটি ইন্টারফেসের সাথে মিল পাওয়া অসম্ভব

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

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

জেনেরিক পদ্ধতিগুলি যে কোনও কোয়েরিকে বাস্তবায়িত করার অনুমতি দেয় এবং তাই ট্রানজিশনের সময়কালে পরিবর্তনগুলি ভাঙ্গতে রোধ করে। লক্ষ্যযুক্ত পদ্ধতিগুলি যখন কলটি বোঝায় তখন আপনি কলটি অনুকূল করতে পারবেন এবং এটি একাধিক পরিষেবা সরবরাহকারীদের জন্য প্রয়োগ করা যেতে পারে।

এই পদ্ধতির নির্দিষ্ট অপ্টিমাইজড কাজগুলি সম্পাদন করে হার্ডওয়্যার বাস্তবায়নগুলির অনুরূপ, অন্যদিকে সফ্টওয়্যার বাস্তবায়ন হালকা কাজ বা নমনীয় বাস্তবায়ন করে।


0

আমি মনে করি যে ডেটা সংগ্রহস্থলের জটিলতা না বাড়িয়ে বৃহত আকারের ক্যোয়ারী ভাষা সরবরাহের ক্ষেত্রে গ্রাফিক্যুয়াল হ'ল একটি ভাল প্রার্থী।

তবে আপাতত গ্রাফকিউএল না যেতে চাইলে অন্য একটি সমাধান রয়েছে। একটি ডিটিও ব্যবহার করে যেখানে কোনও বস্তু প্রক্রিয়াগুলির মধ্যে ডেটা বহন করার জন্য ব্যবহৃত হয়, এক্ষেত্রে পরিষেবা / নিয়ামক এবং সংগ্রহস্থলের মধ্যে থাকা।

একটি মার্জিত উত্তর ইতিমধ্যে উপরে সরবরাহ করা হয়েছে, তবে আমি অন্য একটি উদাহরণ দেওয়ার চেষ্টা করব যা আমি মনে করি এটি সহজ এবং এটি একটি নতুন প্রকল্পের সূচনা পয়েন্ট হিসাবে কাজ করতে পারে।

কোডে প্রদর্শিত হিসাবে, আমাদের CRUD অপারেশনগুলির জন্য কেবল 4 টি পদ্ধতি প্রয়োজন। দ্যfindপদ্ধতি তালিকা এবং বস্তুর যুক্তি ক্ষণস্থায়ী দ্বারা পড়ার জন্য ব্যবহার করা হবে। ব্যাকএন্ড পরিষেবাগুলি কোনও ইউআরএল কোয়েরি স্ট্রিংয়ের ভিত্তিতে বা নির্দিষ্ট পরামিতির উপর ভিত্তি করে সংজ্ঞায়িত ক্যোয়ারী অবজেক্ট তৈরি করতে পারে।

প্রয়োজনে ক্যোয়ারী অবজেক্ট ( SomeQueryDto) নির্দিষ্ট ইন্টারফেস প্রয়োগ করতে পারে। জটিলতা যোগ না করে পরে বাড়ানো সহজ।

<?php

interface SomeRepositoryInterface
{
    public function create(SomeEnitityInterface $entityData): SomeEnitityInterface;
    public function update(SomeEnitityInterface $entityData): SomeEnitityInterface;
    public function delete(int $id): void;

    public function find(SomeEnitityQueryInterface $query): array;
}

class SomeRepository implements SomeRepositoryInterface
{
    public function find(SomeQueryDto $query): array
    {
        $qb = $this->getQueryBuilder();

        foreach ($query->getSearchParameters() as $attribute) {
            $qb->where($attribute['field'], $attribute['operator'], $attribute['value']);
        }

        return $qb->get();
    }
}

/**
 * Provide query data to search for tickets.
 *
 * @method SomeQueryDto userId(int $id, string $operator = null)
 * @method SomeQueryDto categoryId(int $id, string $operator = null)
 * @method SomeQueryDto completedAt(string $date, string $operator = null)
 */
class SomeQueryDto
{
    /** @var array  */
    const QUERYABLE_FIELDS = [
        'id',
        'subject',
        'user_id',
        'category_id',
        'created_at',
    ];

    /** @var array  */
    const STRING_DB_OPERATORS = [
        'eq' => '=', // Equal to
        'gt' => '>', // Greater than
        'lt' => '<', // Less than
        'gte' => '>=', // Greater than or equal to
        'lte' => '<=', // Less than or equal to
        'ne' => '<>', // Not equal to
        'like' => 'like', // Search similar text
        'in' => 'in', // one of range of values
    ];

    /**
     * @var array
     */
    private $searchParameters = [];

    const DEFAULT_OPERATOR = 'eq';

    /**
     * Build this query object out of query string.
     * ex: id=gt:10&id=lte:20&category_id=in:1,2,3
     */
    public static function buildFromString(string $queryString): SomeQueryDto
    {
        $query = new self();
        parse_str($queryString, $queryFields);

        foreach ($queryFields as $field => $operatorAndValue) {
            [$operator, $value] = explode(':', $operatorAndValue);
            $query->addParameter($field, $operator, $value);
        }

        return $query;
    }

    public function addParameter(string $field, string $operator, $value): SomeQueryDto
    {
        if (!in_array($field, self::QUERYABLE_FIELDS)) {
            throw new \Exception("$field is invalid query field.");
        }
        if (!array_key_exists($operator, self::STRING_DB_OPERATORS)) {
            throw new \Exception("$operator is invalid query operator.");
        }
        if (!is_scalar($value)) {
            throw new \Exception("$value is invalid query value.");
        }

        array_push(
            $this->searchParameters,
            [
                'field' => $field,
                'operator' => self::STRING_DB_OPERATORS[$operator],
                'value' => $value
            ]
        );

        return $this;
    }

    public function __call($name, $arguments)
    {
        // camelCase to snake_case
        $field = strtolower(preg_replace('/(?<!^)[A-Z]/', '_$0', $name));

        if (in_array($field, self::QUERYABLE_FIELDS)) {
            return $this->addParameter($field, $arguments[1] ?? self::DEFAULT_OPERATOR, $arguments[0]);
        }
    }

    public function getSearchParameters()
    {
        return $this->searchParameters;
    }
}

ব্যবহারের উদাহরণ:

$query = new SomeEnitityQuery();
$query->userId(1)->categoryId(2, 'ne')->createdAt('2020-03-03', 'lte');
$entities = $someRepository->find($query);

// Or by passing the HTTP query string
$query = SomeEnitityQuery::buildFromString('created_at=gte:2020-01-01&category_id=in:1,2,3');
$entities = $someRepository->find($query);
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.