Giriş: Esnek bir raporlama motoru için gerekli

Enterprise uygulamaları sıklıkla, değişen iş gereksinimlerine adapte olabilecek bir raporlama motoru gerektirir. A statik, zor kodlanmış bir rapor jeneratörü, paydaşların yeni veri kaynakları, filtreler, çıkış biçimleri veya görsel düzenleri talep ettiğinde, Java Springizability'den bir oluşturma modeli gerektirir, bu model gerçek özelleştirilebilirlik sağlar ve karmaşık nesneler inşa etmek için adım atarak nesnenin temsilini sağlar.

Bu makalede, zeminden bir raporlama motoru tasarlayacağız, önceden tanımlanmış bir rapor şablonları ile başlayın ve kalibrasyon, iplik güvenliği ve test gibi gerçek dünya göz önüne alınacaktır.Sonra, onları Spring Boot fasulyesi olarak entegre edeceğiz, işinizle büyümenin bir raporlama alt sistemi ekleyin.

Builder Pattern in Deep

Builder Desen genellikle Özet Fabrika veya Fabrika Yöntemi desenleriyle karıştırılır, ancak amacı farklıdır: bir PDF versiyonu, bir HTML versiyonu veya düz metin versiyonu, istemcinin hangi adımların hangi adımların ve hangi sırayla inşa edilmesine izin verebilir.A klasik bir )

Desendeki Anahtar Katılımcılar:

  • [FONT:0)Ürün[DÜT:1) - Kompleks nesne inşa edilmiştir (ouryuFLT:2).
  • [FONT=0)Yapıcı[DÜT:1) - Özet arayüz inşaat adımlarını tanımlar.
  • [FONT=0)ConcreteBuilder[Dönetici:0)[Döneticileri: Builder arayüzünü uygular ve sonucu elde etmek için bir yöntem sunar.
  • [FONT:0)Yönetici[Dönetici) - Yapı arayüzü kullanarak bina adımları atlar, genellikle varsayılan veya sık kullanılan bir inşaat dizisi alırlar.

Bu endişelerin ayrılması, aynı inşaat sürecinin sadece betonBuilder'i takas ederek farklı temsiller üretebileceği anlamına gelir.Bir raporlama motoru için, bu “summary raporu” veya aynı “tetailed rapor” oluşturmayı mümkün kılar.

Raporlama Motoru Tasarımı

Raporlama motorumuz, ürün ve akıcı bir şekilde inşa edilecek ve akansız bir şekilde 5,5) arayüz. Fluent arabirimleri (method zincirleme) Builder Kalıp için doğal bir uyum ve okunabilir müşteri koduna yol açacaktır.

Raporun Ürününü Tanımlayın

[Üyetim: 16) Sınıf, herhangi bir rapor oluşturmak için gerekli temel verileri tutar. Gerçek bir sistemde, başlık tanımları, altportlar, vb.

public class Report {
 private String title;
 private String dataSource; // e.g., "jdbc/myDb" or "file:/data.csv"
 private String query; // SQL or a query identifier
 private List<String> columns; // columns to display
 private Filter filter; // complex filter object
 private String outputFormat; // PDF, CSV, XLSX, HTML
 private boolean showTotals;

 // private constructor – only builders create instances
 private Report() {}

 // Builder inner class or external – we'll use an external builder
 // Getters (no setters after construction) – omitted for brevity
 public String getTitle() { return title; }
 public String getDataSource() { return dataSource; }
 // etc.
}

Özel yapıcıyı fark edin, bu sadece bir inşaatçı aracılığıyla oluşturulabilir ve her örneği doğru bir şekilde yapılandırılabilir.

Rapor Oluşturucu Interface

İnşaatlı arayüzü her bir opsiyonel konfigürasyon adım için yöntemler beyan ediyor. Yöntem zincirini desteklemek için her setter geri döndürürsün.Son bir şekilde 9.000T:10) yöntemi inşa edilmiş olan USBAR 08'i döndürür.

public interface ReportBuilder {
 ReportBuilder setTitle(String title);
 ReportBuilder setDataSource(String dataSource);
 ReportBuilder setQuery(String query);
 ReportBuilder setColumns(List<String> columns);
 ReportBuilder setFilter(Filter filter);
 ReportBuilder setOutputFormat(String outputFormat);
 ReportBuilder showTotals(boolean showTotals);
 Report build();
}

Bu arayüz kasıtlı olarak geniştir. Beton inşaatçılar belirli yöntemleri görmezden gelebilir (örneğin, basit bir özet rapor üreticisi, binadan önce yapılandırmayı ihmal edebilir veya doğrulamayı seçebilir.

Beton Builders'ı Uygulamayın

Esneklik göstermek için iki inşaatçı uygulayın: aİLFLT:14). ve aurFLT:15). Her ikisi de aynı arayüzü uygular, ancak farklı tür raporlar üretir.

DetaylıReportBuilder

public class DetailedReportBuilder implements ReportBuilder {
 private Report report = new Report();

 @Override
 public ReportBuilder setTitle(String title) {
 report.setTitle(title);
 return this;
 }

 @Override
 public ReportBuilder setDataSource(String dataSource) {
 report.setDataSource(dataSource);
 return this;
 }

 @Override
 public ReportBuilder setQuery(String query) {
 report.setQuery(query);
 return this;
 }

 @Override
 public ReportBuilder setColumns(List<String> columns) {
 report.setColumns(columns);
 return this;
 }

 @Override
 public ReportBuilder setFilter(Filter filter) {
 report.setFilter(filter);
 return this;
 }

 @Override
 public ReportBuilder setOutputFormat(String outputFormat) {
 report.setOutputFormat(outputFormat);
 return this;
 }

 @Override
 public ReportBuilder showTotals(boolean showTotals) {
 report.setShowTotals(showTotals);
 return this;
 }

 @Override
 public Report build() {
 // Validate critical fields
 if (report.getDataSource() == null) {
 throw new IllegalStateException("DataSource must be set");
 }
 // Additional validation logic...
 return report;
 }
}

ÖzetReportBuilder

Özet bir rapor sütunları, filtreyi ve toplamları görmezden gelebilir ve bunun yerine her şeyi tek bir numaraya veya basit bir masaya askıya alabilir.

public class SummaryReportBuilder implements ReportBuilder {
 private String title;
 private String dataSource;
 private String query;
 // other fields are ignored or given defaults

 @Override
 public ReportBuilder setTitle(String title) {
 this.title = title;
 return this;
 }

 @Override
 public ReportBuilder setDataSource(String dataSource) {
 this.dataSource = dataSource;
 return this;
 }

 @Override
 public ReportBuilder setQuery(String query) {
 this.query = query;
 return this;
 }

 // All other setter methods either do nothing or throw UnsupportedOperationException
 @Override
 public ReportBuilder setColumns(List<String> columns) {
 return this; // summary report ignores columns
 }

 // ... similar for filter, outputFormat, showTotals

 @Override
 public Report build() {
 Report report = new Report();
 report.setTitle(title);
 report.setDataSource(dataSource);
 report.setQuery(query);
 report.setOutputFormat("CSV"); // default format
 return report;
 }
}

Bu yaklaşımla, bir müşteri, inşaat sırasını değiştirmeden gerekli çıktı karmaşıklığıyla eşleşen inşaatçıyı seçebilir. Bu, Builder Kalıp'ın özüdür.

Pre-defined Templates için bir yönetmen ekleyin

Genellikle ortak inşaat dizilerini kurmak istersiniz. Bir yönetmen sınıfı bunu yapabilir:

public class ReportDirector {
 private final ReportBuilder builder;

 public ReportDirector(ReportBuilder builder) {
 this.builder = builder;
 }

 public Report constructMonthlySalesReport(String region) {
 return builder
 .setTitle("Monthly Sales – " + region)
 .setDataSource("jdbc/sales_db")
 .setQuery("SELECT * FROM sales WHERE region = :region")
 .setColumns(List.of("Product", "Units Sold", "Revenue"))
 .setFilter(new DateFilter(LocalDate.now().minusMonths(1), LocalDate.now()))
 .setOutputFormat("PDF")
 .showTotals(true)
 .build();
 }

 public Report constructQuickSummary() {
 return builder
 .setTitle("Quick Summary")
 .setDataSource("jdbc/sales_db")
 .setQuery("SELECT count(*) as cnt, sum(revenue) as total FROM sales")
 .setOutputFormat("CSV")
 .build();
 }
}

Yönetmen herhangi birİLFLT:19 ile enjekte edilebilir. Bu, şablonları beton inşaat ayrıntılarından ayırır.

Spring Boot'da Builder Desen: Wiring ve Use

Spring Boot'in bağımlılık enjeksiyonu, inşaatçıları fasulye olarak yönetmeyi ve onları runtime olarak değiştirmeyi kolaylaştırır.

Adım 1: Bahar Beans olarak Define Builders

Beton inşaatçılarımızı, AMAFLT:20 ile not edebilir veya onları bir GÜNDEMT:21'de ilan edebiliriz:

@Configuration
public class ReportConfig {

 @Bean
 @Scope("prototype") // because each builder session uses a fresh instance
 public DetailedReportBuilder detailedReportBuilder() {
 return new DetailedReportBuilder();
 }

 @Bean
 @Scope("prototype")
 public SummaryReportBuilder summaryReportBuilder() {
 return new SummaryReportBuilder();
 }

 @Bean
 @Scope("prototype")
 public ReportDirector reportDirector(ReportBuilder builder) {
 // This bean will not resolve without specifying the builder – we'll discuss later
 return new ReportDirector(builder);
 }
}

[[DÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞ

Belirli bir inşaatçı gerektirir, belirli bir inşaatçı gerektirir veya fabrika modeli kullanabiliriz. Pratik bir yaklaşım birden çok yönetmen fasulyesi tanımlamaktır, bir inşaat türünde biri:

@Bean
public ReportDirector detailedReportDirector(@Qualifier("detailedReportBuilder") ReportBuilder builder) {
 return new ReportDirector(builder);
}

@Bean
public ReportDirector summaryReportDirector(@Qualifier("summaryReportBuilder") ReportBuilder builder) {
 return new ReportDirector(builder);
}

Adım 2: İndüktörlere veya Hizmetlere Uygunluklar / Müdürler

Tipik bir kontrol, bir rapor türü parametresini kabul edebilir ve uygun bileşeni kullanabilir:

@RestController
@RequestMapping("/reports")
public class ReportController {

 @Autowired
 private ReportDirector detailedReportDirector;

 @Autowired
 private ReportDirector summaryReportDirector;

 @GetMapping("/monthly/{region}")
 public ResponseEntity<Report> getMonthlySales(@PathVariable String region) {
 Report report = detailedReportDirector.constructMonthlySalesReport(region);
 // Execute report generation logic...
 return ResponseEntity.ok(report);
 }

 @GetMapping("/summary")
 public ResponseEntity<Report> getSummary() {
 Report report = summaryReportDirector.constructQuickSummary();
 return ResponseEntity.ok(report);
 }
}

Alternatif olarak, inşaatçılara doğrudan enjekte edebilir ve servis katmanının seçim yapmasına izin verebilirsiniz. anahtar nokta: müşteri kodu asla inşaatçı içleri hakkında bilgi sahibi değildir - sadece [[ŞUYUYUMLAR İÇİNDEŞİRELİĞİ:) veya yönetmen yöntemi.

Gelişmiş özelleştirme: Spring's ObjectProvider ile Dinamik Yapılar

Bazen inşaatçı seçimi, konfigürasyon özellikleri veya kullanıcı rolleri üzerine kuruludur. Spring'sETHFLT:30), bir prototip bean lazily enjekte etmeye yardımcı olabilir:

@Service
public class ReportService {

 private final ObjectProvider<DetailedReportBuilder> detailedBuilderProvider;
 private final ObjectProvider<SummaryReportBuilder> summaryBuilderProvider;

 public ReportService(ObjectProvider<DetailedReportBuilder> detailedBuilderProvider,
 ObjectProvider<SummaryReportBuilder> summaryBuilderProvider) {
 this.detailedBuilderProvider = detailedBuilderProvider;
 this.summaryBuilderProvider = summaryBuilderProvider;
 }

 public Report generateReport(String type, Map<String, String> params) {
 ReportBuilder builder;
 if ("detailed".equalsIgnoreCase(type)) {
 builder = detailedBuilderProvider.getObject();
 } else {
 builder = summaryBuilderProvider.getObject();
 }
 // Apply common params (e.g., title, dataSource)
 String title = params.getOrDefault("title", "Report");
 builder.setTitle(title)
 .setDataSource(params.get("dataSource"));
 // Build
 return builder.build();
 }
}

Bu model, her olası inşaatçıyı doğrudan önceden kablolamaktan kaçınır, ancak hala kodu temiz ve test edilebilir tutarken.

Bilinçli ve Thread Güvenliği

[FONTNT] Ürün inşaattan sonra düşünülemez olmalıdır. Çünkü inşaatçılar genellikle tek bir iplikde kullanılır ve paylaşılmaz, inşaatçının kendisini senkronize etmeye gerek yoktur.

İmkansızlığı uygulamak için, sınıf gerçekten hayal edilemez hale getirin:

  • Mark all fields asurFLT:34.
  • Yapıcı aracılığıyla tüm değerleri geçin (prodüktör her şeyi oluşturan özel bir yapılayıcı çağırır).
  • Sadece yavruları sağlayın, setters yok.
  • Koleksiyonlar için (örneğin sütunlar), inşa veya kullanımdaki savunma kopyaları yapmak.)
public class Report {
 private final String title;
 private final String dataSource;
 private final String query;
 private final List<String> columns;
 private final Filter filter;
 private final String outputFormat;
 private final boolean showTotals;

 Report(String title, String dataSource, String query,
 List<String> columns, Filter filter,
 String outputFormat, boolean showTotals) {
 this.title = title;
 this.dataSource = dataSource;
 this.query = query;
 this.columns = columns == null ? List.of() : List.copyOf(columns);
 this.filter = filter;
 this.outputFormat = outputFormat;
 this.showTotals = showTotals;
 }
 // getters...
}

Sonra, bu tam inşacı aracılığıyla, tüm toplanan değerlerin tümünün toplandığını garanti eder.Bu, bir kez inşa edilen raporun değiştirilemeyeceği garanti eder.

Raporlama Motoru Test Etmek

Builder Pattern basit bir test yapar çünkü yanlış inşaatçılara veya teste özel inşaatçılara enjekte edebilirsiniz.Şeffin 660. Ürün için, doğrudan bir inşaatçı kullanarak onu kullanabilirsiniz.In integration testleri için, doğru inşaatçının çağrıldığını ve nihai raporun beklentileri olduğunu doğrulayabilirsiniz.

Bir Beton Builders Testi

@Test
void testDetailedReportBuilder() {
 DetailedReportBuilder builder = new DetailedReportBuilder();
 Report report = builder
 .setTitle("Test")
 .setDataSource("jdbc/test")
 .setOutputFormat("PDF")
 .build();

 assertThat(report.getTitle()).isEqualTo("Test");
 assertThat(report.getDataSource()).isEqualTo("jdbc/test");
 assertThat(report.getOutputFormat()).isEqualTo("PDF");
 assertThat(report.isShowTotals()).isFalse(); // default
}

Mocks ile test

Bir inşaatçı kullanan bir hizmeti test ederken, etkileşimleri doğrulamak için inşaatçı arayüzüne alay edin:

@Test
void testReportServiceUsesBuilderCorrectly() {
 ReportBuilder mockBuilder = mock(ReportBuilder.class);
 when(mockBuilder.setTitle(any())).thenReturn(mockBuilder);
 when(mockBuilder.setDataSource(any())).thenReturn(mockBuilder);
 // ... other stubs
 Report expectedReport = new Report(/* ... */);
 when(mockBuilder.build()).thenReturn(expectedReport);

 ReportService service = new ReportService(/* ... */);
 // inject mockBuilder via a test specific method
 Report result = service.generateReport("detailed", Map.of("title", "Test", "dataSource", "jdbc/db"));

 assertThat(result).isSameAs(expectedReport);
 verify(mockBuilder).setTitle("Test");
 verify(mockBuilder).setDataSource("jdbc/db");
 verify(mockBuilder).build();
}

Performanslar ve Caching

BirFL::42) nesnenin kendisi ucuzdur - sadece bir yapılandırma verileri. Pahalı kısım, temel sorguyu yürütmek, verileri dönüştürmek ve çıktı dosyasını (PDF, XLSX) oluşturmaktır. Bu nedenle, inşaatçı herhangi bir I/O'yu tetiklemelidir.

Aynı rapor yapılandırması tekrar talep edilir (örneğin, aynı bölge için aynı aylık satış raporu), savunma kopyaları olmadan önbellekli olarak kullanılabilir.For caching you can use Spring'surFLT:45).

@Cacheable("reportConfigs")
public Report getMonthlySalesConfig(String region) {
 return detailedReportDirector.constructMonthlySalesReport(region);
}

Yapının kapatılması, inşaatçının sadece ayrı bir dizi parametrede çalışabilmesine izin verir, daha sonraki talepleri sorgu yürütmeden önce hızlandırır.

Diğer Yaklaşımlarla Builder Deseni Karşılaştırmak

Bir raporlama motoru tasarlarken, diğer kalıpları da düşünebilirsiniz:

  • [FONT:0]Freform Yöntemi[[Dönetici:0)[Dönetici:0)) – Bir adımda bir rapor nesnesi oluşturmak için iyi, ancak adım adım adım adım atan konfigürasyonu desteklemez.
  • [FONT:0] Birçok parametre ile Konstructor[[Dönetici: 1 ) - Telescoping yapıtörleri okumanın yanlış olduğunu ve okumak zor.The Builder pattern provides a open, name-parameter style.
  • [FONT:0)JavaBeans deseni (mutlu setler)[Dönetici 1))[Dönetici konfigürasyonuna izin verir, ancak taklit edilebilirliği bozabilir ve kısmen ilkleştirilmiş nesnelere yol açabilir.
  • [FONT:0)Strateji Desen) - Builder ile birleştirilebilir; inşaatçı, bir uygulama veya veri toplama stratejisini kabul edebilir.

Builder modeli, ürün birçok opsiyonel bileşeni olduğu zaman öne çıkar. Ayrıca Open/Kaed Prensibini destekler - mevcut kodu değiştirmeden yeni bir inşaatçı uygulamakla yeni rapor türlerini ekleyebilirsiniz.

Gerçek Dünya Hazırlanıyor

Bir üretim raporlama motoru genellikle bu uzantıları göz önünde bulundurmak için daha basit bir yapılandırmaya ihtiyaç duyar:

  • Alt limanlar için Nested inşaatçılar - her alt sınır kendi inşaatını alabilir.
  • AİLFLT:48) konsepti - önceden yapılandırılmış inşaatçı inşaatçılar bir veritabanı veya YAML dosyalarında depolanırlar.
  • Spring Cloud Config ile birlikte, yeniden işlenmeden rapor şablonlarını değiştirmek için entegrasyon.
  • [FONT:0]Lombok'un [FONTDÜSÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞ

Örneğin, YAML tabanlı bir şablon yüklenebilir:

monthly-sales:
 title: "Monthly Sales - ${region}"
 dataSource: "jdbc/sales"
 query: "SELECT ..."
 columns: ["Product", "Units Sold"]
 outputFormat: "PDF"
 showTotals: true

Bir hizmet bu şablonu parlayabilir ve uygun inşaatçı yöntemleri çağırabilir, raporlama motoru tamamen veriye dayalı hale getirebilir.

Sonuç Sonuç Sonuç Sonuç Sonuç Sonuç Sonuç Sonuç

Bir Java Spring Boot raporlama motoruna uygulanan Builder Desen, rapor yapılandırmalarının ve temsillerinin inşaatı arasında temiz bir ayrılık sağlar. akıcı bir şekilde tanımlayarak, çoklu beton inşaatçıları uygularsınız, dinamik, düzenli olarak teknik borç almadan raporların özelleştirmesini sağlarsınız.

Bu tasarım sadece aşırı derecede basit değildir - yeni bir inşaatçıyı yazarak yeni rapor türlerini ekleyebilirsiniz - ancak aynı zamanda test edilebilir, çünkü inşaatçılar izole edilebilir veya anında izolasyona maruz kalan Java nesnelerdir.

Basit bir pano veya tam bir efledilmiş iş zekası platformu inşa ediyorsanız, Builder pattern size geliştirme gerekliliklerini sağlarken, daha fazla okuma için bir zevk olan bir kod tabanını korurken, resmi olarak bakınız:0)Köpring Framework belgeleri[FLT 1:2 Tasarım Desenleri kitabı).