Чи слід використовувати шар між сервісом і сховищем для чистої архітектури - Spring


9

Я працюю в архітектурі, вона пропонує пропонувати api для відпочинку для веб-клієнтів та мобільних додатків. Я використовую Spring (spring mvc, spring data jpa, ... тощо). Модель домену кодується зі специфікацією JPA.

Я намагаюся застосувати деякі концепції чистої архітектури ( https://8thlight.com/blog/uncle-bob/2012/08/13/the-clean-architecture.html ). Не все, тому що я буду тримати модель домену jpa.

Фактичний потік через шари такий:

Передній <--> Служба API -> Сервіс -> Репозиторій -> БД

  • Передня частина : веб-клієнт, мобільні додатки
  • Служба API : Решта контролерів, тут я використовую перетворювачі, послуги dto та call
  • Сервіс : Інтерфейси з реалізаціями і містять бізнес-логіку
  • Репозиторій : інтерфейси сховища з автоматичними реалізаціями (виконані весняними даними jpa), які містять CRUD-операції та, можливо, деякі запити sql

Мої сумніви: чи варто використовувати додатковий шар між службою та сховищем?

Я планую цей новий потік:

Передній <--> Служба API -> Сервіс -> Постійність -> Репозиторій -> БД

Навіщо використовувати цей шар стійкості? Як сказано в статті про чисту архітектуру, я хотів би мати службову реалізацію (бізнес-логіку або випадок використання), що має доступ до рівня стійкості агностику. І ніякі зміни не знадобляться, якщо я вирішу використовувати інший шаблон «доступу до даних», наприклад, якщо я вирішу припинити використання сховища.

class ProductServiceImpl implements ProductService {
    ProductRepository productRepository;
    void save(Product product) {
        // do business logic
        productRepository.save(product)
    }
}

Тому я думаю використовувати шар стійкості на зразок цього:

class ProductServiceImpl implements ProductService {
    ProductPersistence productPersistence;
    void save(Product product) {
        // do business logic
        productPersistence.save(product)
    }
}

та реалізація такого шару стійкості:

class ProductPersistenceImpl implements ProductPersistence {
    ProductRepository productRepository;
    void save(Product product) {
        productRepository.save(product)
    }
}

Тому мені потрібно лише змінити реалізацію шару стійкості, залишивши сервіс без змін. Сумівся з тим, що Репозиторій пов'язаний з рамкою.

Як ти гадаєш? Дякую.


3
сховище є шаром абстракції. додавши ще одну допомогу
Еван

О, але мені доведеться використовувати інтерфейс, запропонований Spring, я маю на увазі назви методів сховища. І якщо я хочу змінити сховище, мені доведеться зберігати виклики, ні?
Алехандро

мені здається, що сховище весни не змушує виставляти весняні об’єкти. Просто використовуйте його для реалізації агностичного інтерфейсу
Еван

Відповіді:


6

Передній <--> Служба API -> Сервіс -> Репозиторій -> БД

Правильно. Це основна конструкція шляхом сегрегації проблем, запропонованих Spring Framework. Отже, ви в " Весняному правильному шляху ".

Незважаючи на те, що Репозиторії часто використовуються як DAO, правда полягає в тому, що розробники Spring взяли поняття репозиторію з DDD Еріка Еванса. Інтерфейси сховища часто виглядають дуже схожими на DAO через методи CRUD та через те, що багато розробників прагнуть зробити інтерфейси сховищ такими загальними, що, зрештою, вони не мають різниці з EntityManager (справжній DAO тут) 1, а підтримка запити та критерії.

У перекладі на компоненти Spring, ваш дизайн схожий на

@RestController > @Service > @Repository >  EntityManager

Репозиторій - це вже абстракція між службами та сховищами даних. Розширюючи інтерфейси сховища Spring Data JPA, ми реалізуємо цю конструкцію неявно. Коли ми це робимо, ми сплачуємо податок: тісний зв’язок із компонентами Spring. Крім того, ми порушуємо LoD та YAGNI, успадковуючи кілька методів, які нам можуть не знадобитися чи не хотіти б. Не кажучи вже про те, що такий інтерфейс не дає нам ніякого цінного розуміння потреб домену, який вони обслуговують.

Однак, розширення сховищ JPA Spring Data не є обов'язковим, і ви можете уникнути цих компромісів, застосовуючи більш просту і власну ієрархію класів.

    @Repository
    public class MyRepositoryImpl implements MyRepository{
        private EntityManager em;

        @Autowire
        public MyRepository (EntityManager em){    
             this.em = em;
        }

        //Interface implentation
        //...
    }

Зміна джерела даних тепер просто потребує нової реалізації, яка замінює EntityManager іншим джерелом даних .

    //@RestController > @Service > @Repository >  RestTemplate

    @Repository
    public class MyRepositoryImpl implements MyRepository{
        private RestTemplate rt;

        @Autowire 
        public MyRepository (RestTemplate rt){    
             this.rt = rt;
        }

        //Interface implentation
        //...
    }
    //@RestController > @Service > @Repository >  File

    @Repository
    public class MyRepositoryImpl implements MyRepository{

        private File file; 
        public MyRepository (File file){    
            this.file = file;
        }

        //Interface implentation
        //...
    }
    //@RestController > @Service > @Repository >  SoapWSClient

    @Repository
    public class MyRepositoryImpl implements MyRepository{

        private MyWebServiceClient wsClient; 

        @Autowire
        public MyRepository (MyWebServiceClient  wsClient){    
               this.wsClient = wsClient;
        }

        //Interface implentation
        //...
    }

і так далі. 2

Повернувшись до питання, чи варто додати ще один шар абстракції, я б сказав «ні», оскільки це не потрібно. Ваш приклад лише додає більшої складності. Пропонований вами шар перетворюється на проксі-сервер між службами та репозиторіями або як псевдо-сервіс-репозитарій- шар, коли потрібна певна логіка, і ви не можете розмістити її.


1: На відміну від багатьох розробників, інтерфейси сховища можуть бути абсолютно різними один від одного, оскільки кожне сховище обслуговує різні потреби домену. У Spring Data JPA роль DAO відіграє EntityManager . Він керує сеансами, доступом до DataSource , відображенням тощо.

2: Аналогічне рішення покращує інтерфейси сховища Spring, поєднуючи їх із спеціальними інтерфейсами. Для отримання додаткової інформації шукайте BaseRepositoryFactoryBean та @NoRepositoryBean . Однак я вважаю такий підхід громіздким і заплутаним.


3

Найкращий спосіб довести, що конструкція гнучка - це її згинати.

Ви хочете, щоб у вашому коді було місце, яке відповідає за постійність, але не пов'язане з ідеєю використання сховища. Чудово. Наразі це не робить нічого корисного ... зітхніть, чудово.

Гаразд, давайте перевірити, чи не робив цей шар шунту. Створіть плоский шар файлу, який збереже ваші продукти у файлах. Тепер, куди йде цей новий шар у цій конструкції?

Ну, воно повинне бути в змозі перейти туди, де є БД. Зрештою, нам більше не потрібна БД, оскільки ми використовуємо плоскі файли. Але для цього теж не потрібно було сховища.

Поміркуйте, можливо, сховище - це деталь реалізації. Зрештою, я можу поговорити з БД без використання шаблону репозиторію.

Front end <--> API Service -> Service -> Repository -> DB

Front end <--> API Service -> Service -> Repository -> Files

Front end <--> API Service -> Service -> Persistence -> DB

Front end <--> API Service -> Service -> Persistence -> Files

Якщо ви можете зробити все, що працює, не торкаючись Служби, у вас є гнучкий код.

Але не сприймай мого слова. Напишіть це і подивіться, що станеться. Єдиний код, якому я вважаю гнучким, - це гнучкий код.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.