@@ -0,0 +1,80 @@
+package store;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.util.Arrays;
+import java.util.List;
+import store.io.ProductFile;
+import store.io.PromotionFile;
+import store.product.Product;
+import store.utils.ExceptionFactory;
+import store.utils.ExceptionType;
+import store.utils.Transformer;
+import store.utils.Validator;
+
+public class LoadModel {
+    public final static String PRODUCT_LIST_FORMAT = "name,price,quantity,promotion";
+    public final static String PROMOTION_LIST_FROMAT= "name,buy,get,start_date,end_date";
+    public final static String DELIMITER = "\n";
+    final static String RESOURCE_PATH = "./src/main/resources/";
+    final static String PRODUCT_LIST = "products.md";
+    final static String PROMOTION_LIST = "promotions.md";
+    public LoadModel(){
+    }
+
+    public String loadProductList(){
+        String result = readFileAsString(RESOURCE_PATH + PRODUCT_LIST);
+        validateProductList(result);
+        return result;
+    }
+
+    public String loadPromotionList(){
+        String result = readFileAsString(RESOURCE_PATH + PROMOTION_LIST);
+        validatePromotionList(result);
+        return result;
+    }
+
+    public List<String> readFile(String path){
+        Validator.validateFileSize(path);
+        try {
+            return Files.readAllLines(Paths.get(path));
+        } catch (IOException exception){
+            ExceptionFactory.throwIllegalArgumentException(ExceptionType.READ_FILE_FAIL,exception);
+            throw new RuntimeException();
+        }
+    }
+
+    public String readFileAsString(String path){
+
+        List<String> contents = readFile(path);
+        if(contents.isEmpty()) {
+            ExceptionFactory.throwIllegalArgumentException(ExceptionType.EMPTY_FILE);
+        }
+        return Transformer.concatenateList(contents,DELIMITER);
+    }
+
+    public static void validateProductList(String products){
+        if(products.isBlank()) {
+            ExceptionFactory.throwIllegalArgumentException(ExceptionType.EMPTY_FILE);
+        }
+        List<String> productList = Arrays.stream(products.split(DELIMITER)).toList();
+        if(!productList.getFirst().equals(PRODUCT_LIST_FORMAT)){
+            ExceptionFactory.throwIllegalArgumentException(ExceptionType.INVALID_FILE_FORMAT);
+        }
+        Validator.validateEachLine(productList, ProductFile.COLUMN_DELIMITER,ProductFile.values().length);
+    }
+
+    public static void validatePromotionList(String promotions){
+        if(promotions.isBlank()) {
+            ExceptionFactory.throwIllegalArgumentException(ExceptionType.EMPTY_FILE);
+        }
+        List<String> promotionList = Arrays.stream(promotions.split(DELIMITER)).toList();
+        if(!promotionList.getFirst().equals(PROMOTION_LIST_FROMAT)){
+            ExceptionFactory.throwIllegalArgumentException(ExceptionType.INVALID_FILE_FORMAT);
+        }
+        Validator.validateEachLine(promotionList,PromotionFile.COLUMN_DELIMITER,PromotionFile.values().length);
+    }
+
+
+}

리뷰: 첫 줄을 그냥 스킵하기 보다는, 각 플랫폼(products.md 파일 형식)에 맞게 클래스를 선언하여 관리하는게 안정성이 높지 않을까 생각됩니다.

첫 줄이 product.md의 형식을 의미한다고 생각되는데요. 과제에서는 각 행의 열이름을 나타내지만, 다른 플랫폼에서는 열 이름 뿐만 아니라 메타데이터도 포함될 수 도 있다고 생각됩니다.
또한, 첫 줄이 달라지면, 파싱 및 유효성 검증 로직도 달라질 거라 생각됩니다.

따라서, 플랫폼 의존하여 알맞은 로직이 동작하도록 하는 게 중요하고, 첫줄을 읽어서 플랫폼 파일 형식 유효성을 검증하는 것이 제가 선택할 방식입니다.