모델링 연습 리뷰
DESCRIPTION
모델링 연습한 내용 리뷰 자료입니다.TRANSCRIPT
모델링 연습 리뷰
신림프로그래머 최범균 2014-11-25
요구사항 1 • 요청 메시지의 A옵션이 1인 클라이언트C1과 B옵
션이 2인 클라이언트 C2는 서브넷S1의 IP 대역에서 네트워크 설정(IP 포함)을 할당받는다.
• 맥주소가 M3인 클라이언트C3와 M4인 클라이언트 C4는 서브넷S2의 IP 대역 중에서 앞 쪽 절반에서 네트워크 설정을 할당받는다.
• 요청 메시지의 E옵션이 5인 클라이언트C5와 맥주소가 M6인 클라이언트C6은 서브넷S2의 IP 대역 중에서 뒤 쪽 절반에서 네트워크 설정을 할당받는다.
• 맥주소 일치가 우선한다. • 임대 IP는 클라이언트 단위로 관리된다.
2
그림으로 보면
C1
C2
C3 (M3)
C4 (M4)
C5
IP 대역 S1
IP 대역 S2
A=1
B=1
C6 (M6)
E=1
3
그림에서 도출
C1
C2
C3 (M3)
C4 (M4)
C5
IP 대역 S1
IP 대역 S2
A=1
B=1
C6 (M6)
E=1
옵션으로 클라이언트 구분
맥주소로 클라이언트 구분
다른 조건을 따르는 클라이언트들이 한 대역으로 묶임
한 대역을 나눠 쓸 수 있음
4
요구사항 2 • 응답 옵션 – IP 대역 별로 응답 옵션을 다르게 설정 – IP 풀 별로 응답 옵션을 다르게 설정 – 클라이언트 그룹(클래스) 별로 응답 옵션을 다르
게 설정 – 특정 조건을 충족하는 클라이언트들 별로 응답 옵
션을 다르게 설정 – 같은 응답 옵션이 존재할 경우 우선 순위
• 클라이언트별 > 클라이언트 그룹 > IP 풀 > 대역 • 네트워크 설정(DNS, GW, 서브넷마스크) – IP 대역 별로 네트워크 설정– IP 풀 별로 다른 네트워크 설정
• IP 풀에 네트워크 설정이 있을 경우, 우선 적용
5
최초 끄적임
6
영역 구분
7
설정 영역
8
Optional 써 봄
9
public interface ClientFinder { OpFonal<? extends Client> find(DhcpMessage message); } public interface Client { public OpFonal<ClientClass> getClientClass(); ... } public class ClientClass { private Pool pool; public OpFonal<Pool> getPool() { return OpFonal.ofNullable(pool); } ... }
사용 가능 IP 범위 찾기 public class UsableIpRangeFinderImpl implements UsableIpRangeFinder { private ClientFinder clientFinder; @TransacFonal @Override public RangeResult find(DhcpMessage message) { if (message == null) throw new IllegalArgumentExcepFon(); OpFonal<? extends Client> clientOpt = clientFinder.find(message); OpFonal<Pool> poolOpt = clientOpt.flatMap(c -‐> c.getClientClass()).flatMap(cc -‐> cc.getPool()); if (!poolOpt.isPresent()) return emptyResult(); Pool pool = poolOpt.get(); return new RangeResult( pool.getIpRange(), getNetworkConfig(pool), getMergedOpFons(clientOpt.get())); } private NetworkConfig getNetworkConfig(Pool pool) { NetworkConfig nc = pool.getNetworkConfig(); return nc != null ? nc : pool.getSubnect().getNetworkConfig(); } private DhcpOpFons getMergedOpFons(Client client) { ... // 다음 장에 코드 표시 }
10
// OpFonal 대신 null 사용 경우 Client client = clientFinder.find(message); if (client == null) return emptyResult(); if (client.getClientClass() == null) return emptyResult(); Pool pool = client.getClientClass().getPool(); if (pool == null) return emptyResult();
응답 옵션 생성 부분
11
// UsableIpRangeFinderImpl 클래스 private DhcpOpFons getMergedOpFons(Client client) { DhcpOpFons cOpFons = client.getDhcpOpFons(); ClientClass clientClass = client.getClientClass().get(); DhcpOpFons ccOpFons = clientClass.getDhcpOpFons(); Pool pool = clientClass.getPool().get(); DhcpOpFons poolOpFons = pool.getDhcpOpFons(); DhcpOpFons subnetOpFons = pool.getSubnect().getDhcpOpFons(); return subnetOp9ons.merge(poolOpFons).merge(ccOpFons).merge(cOpFons); }
// DhcpOpFons 클래스 public DhcpOpFons merge(DhcpOpFons other) { DhcpOpFons newOpFons = new DhcpOpFons(this.opFonMap); if (other == null || other.isEmpty()) return newOpFons; other.opFonMap.values().forEach(ov -‐> newOpFons.add(ov)); return newOpFons; }
클라이언트 찾기 • 두 종류의 클라이언트 매칭
12
public class HardwareAddressClient implements Client { private HardwareAddress hardwareAddress; ... }
public class MatchClient implements Client { private List<MatchPredicate> predicates = new ArrayList<>(); public boolean match(DhcpMessage message) { return predicates.stream() .allMatch(p -‐> p.match(message)); } ... }
1. 하드웨어 주소
2. 조건 일치
클라이언트 찾기
13
public class ClientFinderImpl implements ClientFinder { private HardwareAddressClientRepository hardwareAddressClientRepository; private MatchClientRepository matchClientRepository; @Override public OpFonal<? extends Client> find(DhcpMessage message) { if (message == null) return OpFonal.empty(); // 맥주소 일치가 먼저 OpFonal<HardwareAddressClient> hwAddrClient = hardwareAddressClientRepository.findByHardwareAddress(message.getChaddr()); if (hwAddrClient.isPresent()) return hwAddrClient; // 맥주소 일치 없으면, 조건 충족하는 Client 검색 List<MatchClient> clients = matchClientRepository.findAll(); for (MatchClient mclient : clients) if (mclient.match(message)) return OpFonal.of(mclient); return OpFonal.empty(); } ...
MatchClient의 MatchPredicate • 다양한 MatchPredicate 가능성 – 인터페이스/종류별 하위 타입으로 설계
14
JPA 적용 • DB 연동은 JPA를 사용하기로 결정 – 기본 데이터 타입은 단순 매핑 설정 사용 – InetAddress 등에 커스텀 변환기 사용
• 적용하기 위한 몇 가지 코드 변경 – DhcpOptions 필드 à List<DhcpOption> – MatchPredicate 계층 à 단일 클래스
15
일부 매핑 설정 예
16
@Entity @Table(name = "SUBNET_CONFIG") public class SubnetConfig { @Id @Column(name = "ID") private Long id; @Column(name = "SUBNET") @Convert(converter = SubnetConverter.class) private Subnet subnet; @Embedded private NetworkConfig networkConfig; ...// DhcpOptions는 다다다...음 장에
@Embeddable public class NetworkConfig { @Column(name = "SUBNETMASK") @Convert(converter = SubnetMaskConverter.class) private SubnetMask subnetMask; @Column(name = "GATEWAY") @Convert(converter = InetAddressConverter.class) private InetAddress gateway; @Column(name = "DNSLIST") @Convert(converter = IpListConverter.class) private IpList domainServers;
SUBNET_CONFIG -‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐ ID: int SUBNET: VARCHAR SUBNETMASK: VARCHAR GATEWAY: VARCHAR DNSLIST: VARCHAR
커스텀 변환기 • 값 타입과 DB 한 개 컬럼 간의 변환 위함
17
@Entity @Table(name = "SUBNET_CONFIG") public class SubnetConfig { @Id @Column(name = "ID") private Long id; @Column(name = "SUBNET") @Convert(converter = SubnetConverter.class) private Subnet subnet; ... } public class Subnet { private InetAddress networkAddress; private int bits; ... }
SUBNET_CONFIG -‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐ ID: int SUBNET: VARCHAR SUBNETMASK: VARCHAR GATEWAY: VARCHAR DNSLIST: VARCHAR
Java new Subnet("192.168.0.1", 24)
컬럼 값 192.168.0.1/24
커스텀 컨버터 구현 예
18
@Converter public class SubnetConverter implements AttributeConverter<Subnet, String> { @Override public String convertToDatabaseColumn(Subnet subnet) { if (subnet == null) return null; return subnet.toString(); } @Override public Subnet convertToEntityAttribute(String dbData) { if (dbData == null || dbData.isEmpty()) return null; return new Subnet(dbData); } }
커스텀 컨버터 구현 예
19
// List<InetAddress>와 "123.0.2.1,192.168.0.254" DB 데이터 간 변환 처리 @Converter public class IpListConverter implements AttributeConverter<IpList, String> { @Override public String convertToDatabaseColumn(IpList attribute) { if (attribute == null || attribute.isEmpty()) return ""; else return attribute.toString(); } @Override public IpList convertToEntityAttribute(String dbData) { if (dbData == null) return null; String[] ips = dbData.split(","); List<InetAddress> addresses = new ArrayList<>(); for (String ip : ips) { try { addresses.add(InetAddress.getByName(ip)); } catch (UnknownHostException e) { throw new RuntimeException(....생략, e); } } return new IpList(addresses); } }
JPA 적용 과정에서의 모델 변화 • DhcpOptions 구현 변경 – 모델과 DB간 불일치
20
@Entity @Table(name = "CLIENT_MATCH") public class MatchClient implements Client { // @Embedded ?? private DhcpOptions options; public DhcpOptions getDhcpOptions() { return options; } ... } // @Embeddable ?? public class DhcpOptions { // ?? private Map<DhcpOption, OptionValue> optionMap; }
CM_DHCP_OPTIONS -‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐ CM_ID: int LIST_INDEX: int OPTION_CODE: int OPTION_VALUE: String
키 vs 인덱스
관리 화면에서 입력한 순서대로 보여줄 필요
JPA 적용 과정에서의 모델 변화 • DhcpOptions 구현 변경 – 모델과 DB간 불일치
21
@Entity @Table(name = "CLIENT_MATCH") public class MatchClient implements Client { private DhcpOptions options; public DhcpOptions getDhcpOptions() { return options; } ... }
@ElementCollection @CollectionTable(name = "CLIENT_MATCH_DHCP_OPTION", joinColumns = @JoinColumn(name = "CLIENT_MATCH_ID")) @OrderColumn(name = "LIST_INDEX") private List<OptionValue> options = new ArrayList<>(); public DhcpOptions getDhcpOptions() { // 메서드 시그너쳐 유지 return new DhcpOptions(options); }
CM_DHCP_OPTIONS -‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐-‐ CM_ID: int LIST_INDEX: int OPTION_CODE: int OPTION_VALUE: String
JPA 적용 과정에서의 모델 변화 • MatchClient 구현 변경 – @Embeddable 타입의 상속 지원하지 않음 – 계층을 단일 클래스로 변경
22
23
끝