Search code examples
javaspringoopstructurepipeline

Java/Spring -> how to structure (Design Pattern) the relationship between multiple classes involved in the same process


TLDR; Does my DailyRecordDataManager class have a code smell? Is it a 'God Class'? and how can I improve the structure?

Hi,

I'm working on my first project with Spring. It's going to fetch covid-19 data from the Madrid (where I live) government website, organise it by locality, and serve it up through an API.

Here is a sample of the JSON data I'm consuming.

{
  "codigo_geometria": "079603",
  "municipio_distrito": "Madrid-Retiro",
  "tasa_incidencia_acumulada_ultimos_14dias": 23.4668991007149,
  "tasa_incidencia_acumulada_total": 1417.23308497532,
  "casos_confirmados_totales": 1691,
  "casos_confirmados_ultimos_14dias": 28,
  "fecha_informe": "2020/07/01 09:00:00"
}

Each JSON object is a a record of cases and the infection rate on a specific date and for a specific municipal district.

After fetching the data the program: parses it, filters it, trims/rounds some properties, maps it by locality, uses it to create an object for each locality (DistrictData), and writes the locality DistrictData objects to a MonoDB instance.

At the moment I have split each of these steps in the process separate classes, as per the single responsibility principle. As can be seen in the linked screenshot:

screenshot of intellij package structure

My problem is I don't know how to link these multiple classes together.

At the moment I have a Manager class which smells a bit like a God Class to me:

@Service 
public class DailyRecordDataManager implements DataManager {

    private final Logger logger = LoggerFactory.getLogger(DailyRecordDataManager.class);

    private final DailyRecordDataCollector<String> dataCollector;
    private final DataVerifier<String> dataVerifier;
    private final JsonParser<DailyRecord> dataParser;
    private final DataFilter<List<DailyRecord>> dataFilter;
    private final DataTrimmer<List<DailyRecord>> dataTrimmer;
    private final DataSorter<List<DailyRecord>> dataSorter;
    private final DataMapper<List<DailyRecord>> dataMapper;
    private final DataTransformer dataTransformer;
    private final DistrictDataService districtDataService;

    public DailyRecordDataManager(DailyRecordDataCollector<String> collector,
                              DataVerifier<String> verifier,
                              JsonParser<DailyRecord> parser,
                              DataFilter<List<DailyRecord>> dataFilter,
                              DataTrimmer<List<DailyRecord>> dataTrimmer,
                              DataSorter<List<DailyRecord>> dataSorter,
                              DataMapper dataMapper,
                              DataTransformer dataConverter,
                              DistrictDataService districtDataService) {
        this.dataCollector = collector;
        this.dataVerifier = verifier;
        this.dataParser = parser;
        this.dataFilter = dataFilter;
        this.dataTrimmer = dataTrimmer;
        this.dataSorter = dataSorter;
        this.dataMapper = dataMapper;
        this.dataTransformer = dataConverter;
        this.districtDataService = districtDataService; 
    }

    @Override
    public boolean newData()  {

        String data = dataCollector.collectData();

        if (!dataVerifier.verifyData(data)) {
            logger.debug("Data is not new.");
            return false;
        }

        List<DailyRecord> parsedData = dataParser.parse(data);

        if (parsedData.size() == 0) {
            return false;
        }

        List<DailyRecord> filteredData = dataFilter.filter(parsedData);

        List<DailyRecord> trimmedData = dataTrimmer.trim(filteredData);

        List<DailyRecord> sortedData = dataSorter.sort(trimmedData);

        Map<String, List<DailyRecord>> mappedData = dataMapper.map(sortedData);

        List<DistrictData> convertedData = dataTransformer.transform(mappedData);

        districtDataService.save(convertedData);

        return true;
        } 
}

I also thought about linking all of the involved classes together in a chain of Injected Dependencies -> so each class has the next class in the process as a dependency and, provided nothing goes wrong with the data, calls that next class in the chain when it's time.

I do also however feel that there must be a design pattern that solves the problem I have!

Thanks!


Solution

  • For anyone who finds this and wonders what I ended up opting for the Pipeline pattern.

    It allowed me to easily organise all of the individual classes I was using into one clean workflow. It also made each stage of the process very easy to test. As well as the pipeline class itself!

    I highly recommend anyone interested in the patter in Java to check out this article, which I used extensively.