مثال للهندسة السداسية في جافا

تم إعداد ترجمة للمقال خصيصًا لطلاب دورة Java Developer .





بصفتنا مطورين ، غالبًا ما يتعين علينا التعامل مع الكود القديم ، الذي يصعب الحفاظ عليه. أنت تعرف مدى صعوبة فهم المنطق البسيط في رمز معكرونة ملتف كبير. يصبح تحسين الشفرة أو تطوير وظائف جديدة كابوسًا للمطور.

سهولة الصيانة هي أحد الأهداف الرئيسية لتصميم البرامج. من الصعب إدارة التعليمات البرمجية التي تتم صيانتها بشكل سيئ. ليس من الصعب فقط التوسع ، ولكن أصبح من الصعب جذب مطورين جدد.

في عالم تكنولوجيا المعلومات ، تسير الأمور بسرعة. إذا طُلب منك تنفيذ وظائف جديدة بشكل عاجل أو إذا كنت تريد التبديل من قاعدة بيانات علائقية إلى NoSQL ، فما هو رد فعلك الأول؟



تزيد التغطية الاختبارية الجيدة من ثقة المطورين بأنه لن تكون هناك مشاكل في الإصدار الجديد. ومع ذلك ، إذا كان منطق عملك متشابكًا مع منطق البنية التحتية ، فقد تكون هناك مشاكل في اختباره.


لماذا أنا؟

ولكن هناك ما يكفي من الحديث الفارغ ، فلنلق نظرة على الهندسة السداسية. سيساعدك استخدام هذا القالب على تحسين قابلية الصيانة والاختبار والفوائد الأخرى.

مقدمة في العمارة السداسية


مصطلح العمارة السداسي (الهندسة المعمارية السداسية ، السداسية) صاغه في عام 2006 أليستير كوكبيرن. يُعرف هذا النمط المعماري أيضًا باسم Ports And Adapters Architecture . بكلمات بسيطة ، تتفاعل مكونات تطبيقك من خلال العديد من نقاط النهاية (المنافذ). لمعالجة الطلبات ، يجب أن يكون لديك محولات تطابق المنافذ.

هنا يمكنك رسم تشابه مع منافذ USB على الكمبيوتر. يمكنك استخدامها إذا كان لديك محول متوافق (شاحن أو محرك أقراص محمول).



يمكن تمثيل هذه البنية بشكل تخطيطي على أنها مسدس مع منطق الأعمال في المركز (في القلب) ، محاطًا بالكائنات التي تتفاعل معها ، والمكونات التي تتحكم فيها ، مما يوفر بيانات الإدخال.

في الواقع ، يتفاعل المستخدمون ومكالمات واجهة برمجة التطبيقات والنصوص الآلية واختبار الوحدة ويقدمون مدخلات لتطبيقك. إذا كان منطق عملك مختلطًا بمنطق واجهة المستخدم ، فستواجه العديد من المشاكل. على سبيل المثال ، سيكون من الصعب تبديل إدخال البيانات من واجهة المستخدم إلى اختبارات الوحدة.

يتفاعل التطبيق أيضًا مع الكائنات الخارجية مثل قواعد البيانات وقوائم انتظار الرسائل وخوادم الويب (عبر مكالمات HTTP API) ، وما إلى ذلك. إذا لزم الأمر ، قم بترحيل قاعدة البيانات أو تحميل البيانات إلى ملف ، يجب أن تكون قادرًا على القيام بذلك دون التأثير على الأعمال. منطق.

كما يوحي الاسم ، " المنافذ والمهايئات " ، هناك "منافذ" يحدث من خلالها التفاعل و "المحولات" هي مكونات تعالج إدخال المستخدم وتحوله إلى "لغة" المجال. تقوم المحولات بتغليف منطق التفاعل مع الأنظمة الخارجية ، مثل قواعد البيانات وقوائم انتظار الرسائل وما إلى ذلك ، وتسهيل الاتصال بين منطق الأعمال والكائنات الخارجية.

يوضح الرسم البياني أدناه الطبقات التي ينقسم إليها التطبيق.



تميز العمارة السداسية ثلاث طبقات في التطبيق: المجال والتطبيق والبنية التحتية:

  • المجال . تحتوي الطبقة على منطق الأعمال الأساسي. لا يحتاج إلى معرفة تفاصيل تنفيذ الطبقات الخارجية.
  • التطبيق . تعمل الطبقة كجسر بين طبقات المجال والبنية التحتية.
  • البنية التحتية . تنفيذ تفاعل المجال مع العالم الخارجي. تبدو الطبقات الداخلية مثل صندوق أسود له.

وفقًا لهذه البنية ، يتفاعل نوعان من المشاركين مع التطبيق: الأساسي (سائق) والثاني (مدفوع). يقوم الفاعلون الرئيسيون بإرسال الطلبات وإدارة التطبيق (على سبيل المثال ، المستخدمون أو الاختبارات الآلية). توفر الشبكات الثانوية البنية التحتية للتواصل مع العالم الخارجي (هذه هي محولات قاعدة البيانات ، عملاء TCP أو HTTP).
يمكن تمثيل ذلك على النحو التالي: يتكون



الجانب الأيسر من السداسي من مكونات توفر مدخلات للمجال ("تتحكم" في التطبيق) ، ويتكون الجانب الأيمن من مكونات يتحكم فيها تطبيقنا.

مثال


دعونا نصمم تطبيقًا سيخزن مراجعات الأفلام. يجب أن يكون المستخدم قادرًا على إرسال طلب باسم الفيلم والحصول على خمس مراجعات عشوائية.

من أجل البساطة ، سنقوم بإنشاء تطبيق وحدة تحكم مع تخزين البيانات في ذاكرة الوصول العشوائي. سيتم عرض استجابة المستخدم على وحدة التحكم.

لدينا مستخدم (مستخدم) يرسل طلبًا إلى التطبيق. وبالتالي ، يصبح المستخدم "مدير" (سائق). يجب أن يكون التطبيق قادرًا على استقبال البيانات من أي نوع من التخزين وإخراج النتائج إلى وحدة التحكم أو إلى ملف. ستكون الكائنات المدارة (مدفوعة) "مستودع البيانات" ( IFetchMovieReviews) و "طابعة الاستجابة" ( IPrintMovieReviews).

يوضح الشكل التالي المكونات الرئيسية لتطبيقنا.



على اليسار هي المكونات التي توفر إدخال البيانات في التطبيق. على اليمين توجد المكونات التي تسمح لك بالتفاعل مع قاعدة البيانات ووحدة التحكم.

دعونا نلقي نظرة على رمز التطبيق.

منفذ التحكم

public interface IUserInput {
    public void handleUserInput(Object userCommand);
}

الموانئ المدارة

public interface IFetchMovieReviews {
    public List<MovieReview> fetchMovieReviews(MovieSearchRequest movieSearchRequest);
}

public interface IPrintMovieReviews {
    public void writeMovieReviews(List<MovieReview> movieReviewList);
}



سيتم الحصول على محولات المنافذ المدارة من مستودع الأفلام (MovieReviewsRepo). ستكون مراجعات الأفلام المعروضة على وحدة التحكم فئة ConsolePrinter. دعنا ننفذ الواجهتين أعلاه.

public class ConsolePrinter implements IPrintMovieReviews {
    @Override
    public void writeMovieReviews(List<MovieReview> movieReviewList) {
        movieReviewList.forEach(movieReview -> {
            System.out.println(movieReview.toString());
        });
    }
}

public class MovieReviewsRepo implements IFetchMovieReviews {
    private Map<String, List<MovieReview>> movieReviewMap;

    public MovieReviewsRepo() {
        initialize();
    }

    public List<MovieReview> fetchMovieReviews(MovieSearchRequest movieSearchRequest) {

        return Optional.ofNullable(movieReviewMap.get(movieSearchRequest.getMovieName()))
            .orElse(new ArrayList<>());
    }

    private void initialize() {
        this.movieReviewMap = new HashMap<>();
        movieReviewMap.put("StarWars", Collections.singletonList(new MovieReview("1", 7.5, "Good")));
        movieReviewMap.put("StarTreck", Arrays.asList(new MovieReview("1", 9.5, "Excellent"), new MovieReview("1", 8.5, "Good")));
    }
}

نطاق


المهمة الرئيسية لتطبيقنا هي معالجة طلبات المستخدمين. تحتاج إلى الحصول على الأفلام ومعالجتها ونقل النتائج إلى "الطابعة". في الوقت الحالي ، لدينا وظيفة واحدة فقط - البحث عن الأفلام. لمعالجة طلبات المستخدم ، سنستخدم الواجهة القياسية Consumer.

دعونا نلقي نظرة على الفصل الرئيسي MovieApp.

public class MovieApp implements Consumer<MovieSearchRequest> {
    private IFetchMovieReviews fetchMovieReviews;
    private IPrintMovieReviews printMovieReviews;
    private static Random rand = new Random();

    public MovieApp(IFetchMovieReviews fetchMovieReviews, IPrintMovieReviews printMovieReviews) {
        this.fetchMovieReviews = fetchMovieReviews;
        this.printMovieReviews = printMovieReviews;
    }

    private List<MovieReview> filterRandomReviews(List<MovieReview> movieReviewList) {
        List<MovieReview> result = new ArrayList<MovieReview>();
        // logic to return random reviews
        for (int index = 0; index < 5; ++index) {
            if (movieReviewList.size() < 1)
                break;
            int randomIndex = getRandomElement(movieReviewList.size());
            MovieReview movieReview = movieReviewList.get(randomIndex);
            movieReviewList.remove(movieReview);
            result.add(movieReview);
        }
        return result;
    }

    private int getRandomElement(int size) {
        return rand.nextInt(size);
    }

    public void accept(MovieSearchRequest movieSearchRequest) {
        List<MovieReview> movieReviewList = fetchMovieReviews.fetchMovieReviews(movieSearchRequest);
        List<MovieReview> randomReviews = filterRandomReviews(new ArrayList<>(movieReviewList));
        printMovieReviews.writeMovieReviews(randomReviews);
    }
}

الآن نحدد فئة CommandMapperModelستقوم بتعيين الأوامر للمعالجات.

public class CommandMapperModel {
    private static final Class<MovieSearchRequest> searchMovies = MovieSearchRequest.class;

    public static Model build(Consumer<MovieSearchRequest> displayMovies) {
        Model model = Model.builder()
            .user(searchMovies)
            .system(displayMovies)
            .build();

        return model;
    }
}

محولات منفذ التحكم


سوف يتفاعل المستخدم مع نظامنا من خلال الواجهة IUserInput. سيتم استخدام ModelRunnerالتنفيذ وتفويض التنفيذ.

public class UserCommandBoundary implements IUserInput {
    private Model model;

    public UserCommandBoundary(IFetchMovieReviews fetchMovieReviews, IPrintMovieReviews printMovieReviews) {
        MovieApp movieApp = new MovieApp(fetchMovieReviews, printMovieReviews);
        model = CommandMapperModel.build(movieApp);
    }

    public void handleUserInput(Object userCommand) {
        new ModelRunner().run(model)
            .reactTo(userCommand);
    }
}

الآن دعونا نلقي نظرة على المستخدم الذي يستخدم الواجهة المذكورة أعلاه.

public class MovieUser {
    private IUserInput userInputDriverPort;

    public MovieUser(IUserInput userInputDriverPort) {
        this.userInputDriverPort = userInputDriverPort;
    }

    public void processInput(MovieSearchRequest movieSearchRequest) {
        userInputDriverPort.handleUserInput(movieSearchRequest);
    }
}

تطبيق


بعد ذلك ، قم بإنشاء تطبيق وحدة تحكم. تتم إضافة محولات مدارة على أنها تبعيات. سيقوم المستخدم بإنشاء وإرسال طلب إلى التطبيق. سوف يتلقى التطبيق البيانات ومعالجة وعرض استجابة لوحدة التحكم.

public class Main {

    public static void main(String[] args) {
        IFetchMovieReviews fetchMovieReviews = new MovieReviewsRepo();
        IPrintMovieReviews printMovieReviews = new ConsolePrinter();
        IUserInput userCommandBoundary = new UserCommandBoundary(fetchMovieReviews, printMovieReviews);
        MovieUser movieUser = new MovieUser(userCommandBoundary);
        MovieSearchRequest starWarsRequest = new MovieSearchRequest("StarWars");
        MovieSearchRequest starTreckRequest = new MovieSearchRequest("StarTreck");

        System.out.println("Displaying reviews for movie " + starTreckRequest.getMovieName());
        movieUser.processInput(starTreckRequest);
        System.out.println("Displaying reviews for movie " + starWarsRequest.getMovieName());
        movieUser.processInput(starWarsRequest);
    }

}

ما يمكن تحسينه وتغييره


  • في تنفيذنا ، يمكنك التبديل بسهولة من مخزن بيانات إلى آخر. يمكن إدخال تطبيق التخزين في الشفرة دون تغيير منطق الأعمال. على سبيل المثال ، يمكنك نقل البيانات من الذاكرة إلى قاعدة بيانات عن طريق كتابة محول قاعدة بيانات.
  • بدلاً من الإخراج إلى وحدة التحكم ، يمكنك تنفيذ "طابعة" ، والتي ستكتب البيانات إلى ملف. في مثل هذا التطبيق متعدد الطبقات ، يصبح من الأسهل إضافة وظائف وإصلاح الأخطاء.
  • لاختبار منطق الأعمال ، يمكنك كتابة اختبارات معقدة. يمكن اختبار المحولات في عزلة. وبالتالي ، من الممكن زيادة تغطية الاختبار الإجمالية.

استنتاج


يمكن ملاحظة المزايا التالية للهندسة السداسية:

  • المرافقة - الطبقات المقترنة بشكل فضفاض ومستقلة. يصبح من السهل إضافة ميزات جديدة إلى طبقة واحدة دون التأثير على طبقات أخرى.
  • قابلية الاختبار - تتم كتابة اختبارات الوحدة ببساطة وبسرعة. يمكنك كتابة اختبارات لكل طبقة باستخدام كائنات كعب تحاكي التبعيات. على سبيل المثال ، يمكننا إزالة التبعية لقاعدة البيانات عن طريق إنشاء مستودع بيانات في الذاكرة.
  • القدرة على التكيف - يصبح منطق العمل الرئيسي مستقلاً عن التغييرات في الكائنات الخارجية. على سبيل المثال ، إذا كنت بحاجة إلى الترحيل إلى قاعدة بيانات أخرى ، فلن نحتاج إلى إجراء تغييرات على المجال. يمكننا عمل محول مناسب لقاعدة البيانات.

المراجع



هذا كل شئ. نراكم في الدورة !

All Articles