직장에서 매우 흥미로운 상황을 겪었고 여기에 해결책을 공유하고 싶었습니다.
일련의 데이터를 처리해야 한다고 상상해 보세요. 그리고 이 데이터 세트를 처리하기 위해 이에 대한 몇 가지 다른 전략이 있습니다. 예를 들어, S3에서 데이터 컬렉션을 가져오거나 로컬 저장소 내의 예제를 가져오거나 입력으로 전달하는 방법에 대한 전략을 만들어야 했습니다.
그리고 이 전략을 지시하는 사람은 요청하는 사람입니다.
S3에서 데이터를 가져오고 싶습니다. X일에 H1 시간과 H2 시간 사이에 Abóbora 클라이언트에서 생성된 데이터를 가져옵니다. 이를 충족하는 마지막 3000개의 데이터를 가져옵니다.
그 외의 경우:
여기에 있는 예시 데이터를 10,000번 복사하여 스트레스 테스트를 수행하세요.
또는
제가 이 디렉토리를 갖고 있으니 여러분도 이 디렉토리에 액세스하실 수 있습니다. 해당 디렉토리의 모든 항목을 재귀적으로 하위 디렉토리로 가져옵니다.
그리고 마지막으로:
입력에 있는 이 데이터 단위를 가져와서 사용하세요.
첫 번째 생각은 "Java에서 입력의 형태를 어떻게 정의할 수 있을까?"였습니다.
그리고 저는 프로젝트에 매우 중요한 첫 번째 결론에 도달했습니다. "그거 알아요? 저는 모양을 정의하지 않을 거예요. 이를 처리할 수 있는 Map
게다가 DTO에 어떤 모양도 넣지 않았기 때문에 입력을 자유롭게 실험할 수 있었습니다.
그래서 개념 증명을 확립한 후에는 스트레스 POC에서 벗어나 실제 사용에 가까운 것으로 넘어가야 하는 상황에 도달합니다.
제가 하고 있던 서비스는 규칙을 검증하는 것이었습니다. 기본적으로 규칙을 변경할 때 해당 규칙을 가져와 프로덕션 애플리케이션에서 발생한 이벤트와 일치시켜야 했습니다. 또는 애플리케이션이 변경되고 버그가 없는 경우 동일한 규칙에 대한 결정이 동일한 데이터에 대해 동일하게 유지될 것으로 기대됩니다. 이제 동일한 데이터 세트를 사용하여 동일한 규칙에 대한 결정이 변경되면... 음, 문제가 발생할 수 있습니다.
그래서 규칙 백테스팅을 실행하려면 이 애플리케이션이 필요했습니다. 평가를 위해 데이터와 문제의 규칙을 보내는 실제 애플리케이션을 실행해야 합니다. 이것의 용도는 매우 다양합니다:
그러므로 이벤트 발생에 대한 몇 가지 전략이 필요합니다.
그리고 내 규칙과 다른 전략도 필요합니다.
이런 경우 어떻게 처리하나요? 그럼 사용자가 데이터를 제공하게 하세요!
json-schema에 관해 제가 항상 관심을 가졌던 것이 무엇인지 알고 계시나요? 여기:
{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://json-schema.org/draft/2020-12/schema", "$vocabulary": { //... } }
이 필드는 $로 시작합니다. 제 생각에는 메타데이터를 나타내는 데 사용됩니다. 그렇다면 어떤 전략이 사용되고 있는지 메타데이터를 나타내기 위해 데이터 입력에 이를 사용하는 것은 어떨까요?
{ "dados": { "$strategy": "sample", "copias": 15000 }, //... }
예를 들어, 제가 가지고 있는 데이터의 사본 15,000개를 샘플로 주문할 수 있습니다. 또는 S3에서 몇 가지를 요청하고 Athena에서 쿼리를 수행합니다.
{ "dados": { "$strategy": "athena-query", "limit": 15000, "inicio": "2024-11-25", "fim": "2024-11-26", "cliente": "Abóbora" }, //... }
아니면 로컬 경로에 있나요?
{ "dados": { "$strategy": "localpath", "cwd": "/home/jeffque/random-project-file", "dir": "../payloads/esses-daqui/top10-hard/" }, //... }
그래서 앞으로의 전략 선택을 제가 위임할 수 있습니다.
전략을 다루는 나의 첫 번째 접근 방식은 다음과 같습니다.
public DataLoader getDataLoader(Map<String, Object> inputDados) { final var strategy = (String) inputDados.get("$strategy"); return switch (strategy) { case "localpath" -> new LocalpathDataLoader(); case "sample" -> new SampleDataLoader(resourcePatternResolver_spring); case "athena-query" -> new AthenaQueryDataLoader(athenaClient, s3Client); default -> new AthenaQueryDataLoader(athenaClient, s3Client); } }
그래서 제 건축가는 코드 검토 중에 두 가지 질문을 했습니다.
이것에서 나는 무엇을 이해했는가? 파사드를 사용하면 올바른 코너에 처리를 위임하고... 수동 제어를 포기하는 것이 좋을 것 같은데요?
봄 때문에 많은 마법이 일어납니다. 우리는 Java 전문 지식을 갖춘 Java 하우스에 있으므로 관용적인 Java/Spring을 사용하는 것은 어떨까요? 개인으로서 나가 이해하기 어렵다고 해서 그것이 반드시 복잡하다는 의미는 아닙니다. 이제 Java 종속성 주입 마법의 세계를 살펴보겠습니다.
과거의 모습:
final var dataLoader = getDataLoader(inputDados) dataLoader.loadData(inputDados, workingPath);
현재:
{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://json-schema.org/draft/2020-12/schema", "$vocabulary": { //... } }
그래서 내 컨트롤러 레이어에서는 이를 관리할 필요가 없습니다. 파사드에 맡겨보세요.
그럼 파사드는 어떻게 할 건가요? 글쎄, 시작하려면 모든 개체를 여기에 삽입해야 합니다.
{ "dados": { "$strategy": "sample", "copias": 15000 }, //... }
네, 기본 DataLoader의 경우 @Service 외에 @Primary로 작성합니다. 나머지는 그냥 @Service로 적어두겠습니다.
Spring이 생성자를 호출하고 작동하는 방식을 시험해 보기 위해 getDataLoader를 null로 반환하도록 설정하여 여기에서 테스트하세요. 이제 각 서비스가 어떤 전략을 사용하는지 메타데이터와 함께 기록해야 합니다...
이를 수행하는 방법...
자, 보세요! Java에는 주석이 있습니다! 해당 구성 요소에서 사용되는 전략이 포함된 런타임 주석을 만들 수 있습니다!
따라서 AthenaQueryDataLoader에서 다음과 같은 것을 가질 수 있습니다.
{ "dados": { "$strategy": "athena-query", "limit": 15000, "inicio": "2024-11-25", "fim": "2024-11-26", "cliente": "Abóbora" }, //... }
별칭도 가질 수 있는데 왜 안 되나요?
{ "dados": { "$strategy": "localpath", "cwd": "/home/jeffque/random-project-file", "dir": "../payloads/esses-daqui/top10-hard/" }, //... }
그리고 보여주세요!
그런데 이 주석을 만드는 방법은 무엇일까요? 글쎄요, 문자열 벡터인 속성이 필요합니다(Java 컴파일러는 이미 단일 문자열을 제공하고 이를 위치 1의 벡터로 변환하는 작업을 처리하고 있습니다). 기본값은 값입니다. 다음과 같습니다:
public DataLoader getDataLoader(Map<String, Object> inputDados) { final var strategy = (String) inputDados.get("$strategy"); return switch (strategy) { case "localpath" -> new LocalpathDataLoader(); case "sample" -> new SampleDataLoader(resourcePatternResolver_spring); case "athena-query" -> new AthenaQueryDataLoader(athenaClient, s3Client); default -> new AthenaQueryDataLoader(athenaClient, s3Client); } }
주석 필드의 값이 아닌 경우 명시적으로 지정해야 하며 이는 EstrategiaFeia 주석에서처럼 보기 흉하게 보일 것입니다.
final var dataLoader = getDataLoader(inputDados) dataLoader.loadData(inputDados, workingPath);
제 생각에는 그다지 자연스럽지 않은 것 같아요.
그렇습니다. 그래도 다음이 필요합니다.
주석을 추출하려면 객체 클래스에 대한 액세스 권한이 필요합니다.
dataLoaderFacade.loadData(inputDados, workingPath);
게다가 이 클래스에 Strategy:
와 같은 주석이 추가되었는지 물어봐도 될까요?
@Service // para o Spring gerenciar esse componente como um serviço public class DataLoaderFacade implements DataLoader { public DataLoaderFacade(DataLoader primaryDataLoader, List<DataLoader> dataLoaderWithStrategies) { // armazena de algum modo } @Override public CompletableFuture<Void> loadData(Map<String, Object> input, Path workingPath) { return getDataLoader(input).loadData(input, workingPath); } private DataLoader getDataLoader(Map<String, Object> input) { final var strategy = input.get("$strategy"); // magia... } }
values 필드가 있다는 것을 기억하시나요? 음, 이 필드는 문자열 벡터를 반환합니다.
@Service @Primary @Estrategia("athena-query") public class AthenaQueryDataLoader implements DataLoader { // ... }
쇼! 하지만 한 가지 문제가 있습니다. 전에는 T 유형의 개체가 있었지만 이제는 동일한 개체를 (T, String)[]에 매핑하려고 하기 때문입니다. 스트림에서 이를 수행하는 고전적인 작업은 flatMap입니다. 그리고 Java에서는 갑자기 그런 튜플을 반환하는 것을 허용하지 않지만 이를 사용하여 레코드를 만들 수 있습니다.
다음과 같습니다.
{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://json-schema.org/draft/2020-12/schema", "$vocabulary": { //... } }
전략이 주석으로 추가되지 않은 개체가 있으면 어떻게 되나요? NPE를 제공합니까? 더 나은 방법은 NPE 전에 필터링해 보겠습니다.
{ "dados": { "$strategy": "sample", "copias": 15000 }, //... }
그래도 지도를 만들어야 해요. 그리고 보세요. Java는 이미 이를 위한 수집기를 제공합니다! Collector.toMap(keyMapper, valueMapper)
{ "dados": { "$strategy": "athena-query", "limit": 15000, "inicio": "2024-11-25", "fim": "2024-11-26", "cliente": "Abóbora" }, //... }
지금까지는 괜찮습니다. 그러나 flatMap은 특히 나를 괴롭혔다. 다음과 같은 가능성을 지닌 mapMulti라는 새로운 Java API가 있습니다.
{ "dados": { "$strategy": "localpath", "cwd": "/home/jeffque/random-project-file", "dir": "../payloads/esses-daqui/top10-hard/" }, //... }
뷰티. DataLoader용으로 얻었지만 RuleLoader에서도 동일한 작업을 수행해야 합니다. 아니면 아닐 수도 있나요? 알고 보면 이 코드에는 DataLoader에만 해당되는 내용이 없습니다. 이 코드를 추상화할 수 있습니다!!
public DataLoader getDataLoader(Map<String, Object> inputDados) { final var strategy = (String) inputDados.get("$strategy"); return switch (strategy) { case "localpath" -> new LocalpathDataLoader(); case "sample" -> new SampleDataLoader(resourcePatternResolver_spring); case "athena-query" -> new AthenaQueryDataLoader(athenaClient, s3Client); default -> new AthenaQueryDataLoader(athenaClient, s3Client); } }
순전히 실용적인 이유로 다음 알고리즘을 주석에 배치했습니다.
final var dataLoader = getDataLoader(inputDados) dataLoader.loadData(inputDados, workingPath);
그리고 외관은요? 뭐, 직업도 마찬가지라고 해도 과언이 아니다. 저는 이것을 추상화하기로 결정했습니다:
dataLoaderFacade.loadData(inputDados, workingPath);
외관은 다음과 같습니다.
@Service // para o Spring gerenciar esse componente como um serviço public class DataLoaderFacade implements DataLoader { public DataLoaderFacade(DataLoader primaryDataLoader, List<DataLoader> dataLoaderWithStrategies) { // armazena de algum modo } @Override public CompletableFuture<Void> loadData(Map<String, Object> input, Path workingPath) { return getDataLoader(input).loadData(input, workingPath); } private DataLoader getDataLoader(Map<String, Object> input) { final var strategy = input.get("$strategy"); // magia... } }
위 내용은 Java에서 주석을 사용하여 전략 만들기의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!