3
\$\begingroup\$

I have implemented spring boot rest service which has only one function - get status of invoices by unique ID. In the scope of this task I have implemented Bloom filter algorithm to avoid unnecessary requests to db(especially if we have about 500 millions invoices) but I'm not sure that implemented BloomFilterManager class is better way from architecture and other senses. Maybe, it is better to implement it another place/way.

BlomFilterManager.class:

@Component
public class BloomFilterManager {

  private int expectedInsertions=500000000;

  private double ffp=0.01;

  private BloomFilter<String> bloomFilter;

  public BloomFilterManager() {
    this.bloomFilter = BloomFilter.create(Funnels.stringFunnel(Charset.defaultCharset()), expectedInsertions, ffp);
  }

  public void populate(List<Invoice> invoices) {
    invoices.stream().map(Invoice::getId).forEach(i -> bloomFilter.put(i));
  }

  public BloomFilter<String> getBloomFilter(){
    return bloomFilter;
  }
}

InvoicesGenerator and bloomfilter manager population:

@Component
public class DataGenerator implements ApplicationRunner {

  @Autowired
  BloomFilterManager bloomFilterManager;

  @Autowired
  InvoiceRepository invoiceRepository;

  @Value("${fake.data.count}")
  private Integer fakeDataCount;

  private List<Invoice> invoices = new ArrayList<>();

  @Override
  public void run(ApplicationArguments args) {

    generateFakeInvoices();
    invoices = invoiceRepository.save(invoices);
    bloomFilterManager.populate(invoices);
  }

  private void generateFakeInvoices() {
    Faker faker = new Faker();
    Invoice.PaymentStatus[] paymentStatuses = Invoice.PaymentStatus.values();

    for (int i = 0; i < fakeDataCount; i++) {

      Invoice invoice = new Invoice(
              faker.number().randomNumber(),
              faker.lorem().word(),
              faker.number().randomDigit(),
              new BigDecimal(Math.random()),
              paymentStatuses[faker.number().numberBetween(0, paymentStatuses.length)],
              faker.date().between(new Date(), DateUtils.addDays(new Date(), 13))
      );
      invoices.add(invoice);
    }
  }
}

Then I try to use BloomFilterManager in Service clss:

@Service
public class InvoiceService implements IInvoiceService {

  @Autowired
  private BloomFilterManager bloomFilterManager;

  @Autowired
  private InvoiceRepository invoiceRepository;

  @Override
  public Invoice.PaymentStatus retrievePaymentStatus(String invoiceId) {
    if (!bloomFilterManager.getBloomFilter().mightContain(invoiceId))
      return null;

    return invoiceRepository.findOne(invoiceId).getPaymentStatus();
  }
}

Controller:

@RestController
@RequestMapping("/api/invoice")
public class InvoiceController {

  @Autowired
  private InvoiceService invoiceService;

  @GetMapping(path = "/{invoiceId}/status")
  @ResponseStatus(HttpStatus.OK)
  public PaymentStatus retrievePaymentStatus(@PathVariable String invoiceId) {
    PaymentStatus paymentStatus = invoiceService.retrievePaymentStatus(invoiceId);
    if (paymentStatus == null) {
      throw new InvoiceNotFoundException();
    }
    return paymentStatus;
  }
}

Unit test for service layer:

@RunWith(SpringRunner.class)
@DataJpaTest
public class InvoiceServiceTest {

  @TestConfiguration
  static class InvoiceServiceTestContextConfiguration {
    @Bean
    public InvoiceService invoiceService() {
      return new InvoiceService();
    }

    @Bean
    public BloomFilterManager bloomFilterManager() {
      return new BloomFilterManager();
    }
  }

  @Autowired
  private BloomFilterManager bloomFilterManager;

  @Autowired
  private InvoiceService invoiceService;

  @Autowired
  private InvoiceRepository invoiceRepository;

  @Test
  public void retrievePaymentStatusReturnsPaidStatus() {
    Faker faker = new Faker();

    Invoice invoice = invoiceRepository.save(new Invoice(
            faker.number().randomNumber(),
            faker.lorem().word(),
            faker.number().randomDigit(),
            new BigDecimal(Math.random()),
            Invoice.PaymentStatus.PAID,
            faker.date().between(new Date(), DateUtils.addDays(new Date(), 13))
    ));

    bloomFilterManager.getBloomFilter().put(invoice.getId());

    Invoice.PaymentStatus actualPaymentStatus = invoiceService.retrievePaymentStatus(invoice.getId());

    assertEquals(Invoice.PaymentStatus.PAID, actualPaymentStatus);
  }

  @Test
  public void retrievePaymentStatusReturnsNull() {
    Invoice.PaymentStatus actualPaymentStatus = invoiceService.retrievePaymentStatus("");

    assertEquals(null, actualPaymentStatus);
  }

}

Also I cannot mock bloom filter manager in unit test in such a way

when(bloomFilterManager.getBloomFilter().mightContaint(anyString())).therReturn(true);

Any suggestions what and how I can improve my code? Best practices or any other suggestion are also welcome

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.