2016년 10월 26일 수요일

xamarin - Xamarin.forms는 버려라

진심으로.

visual studio에서 New project로 생성한 프로젝트가 빌드조차 안된다.
이유도 모르겠다.

그나마 빌드되는게 Native정도.

이건 android 각종 resource를 포팅해야 하는데,
intelliSense가 구려서인지 정보가 다 없어서 그런지 모르겠지만
타이핑할 양이 훨씬 많다 -_-;

layout 폴더 아래 있는 xaml 파일들 내용 자체는
android에서 생성한 xml 파일들과 내용이 같다.

그러니까 그냥 layout은 android studio에서 그린 담에 복사해 넣자.그게 속편하다, 진심으로.


그리고 각종 리소스들은 Build 하기 전에는 인식을 못한다.
이것때문에도 며칠 삽질을

이쯤에서 이걸 도대체 왜 xamarin으로 만들고 있어야 하지 하는 생각이 뇌리를 스치운다.
iOS 앱 만들려면 결국 또 Storyboard로 그려야 하겠구만.


2016년 10월 24일 월요일

Springboot Template engine. JSP vs Velocity vs Thymeleaf (1)

스프링부트와 템플릿 엔진 이야기

1. JSP
jsp를 쓰려니까 뭔가 추가해야할 것이 많더라. 그래서 그냥 패스.

2. Velocity
그래서 velocity를 썼는데, 1.4.0Release 버전부터 deprecated 된것이 아닌가.
이미 적용한 데는 놔두고, 다음 작업할 곳은 다른 엔진을 찾아 보기로 했다.

3. Thymeleaf
여러가지가 있었던것 같지만, freemaker, thymeleaf 정도 손에 꼽혔다.

구글 트렌드를 한번 검색해보니 대충 이렇더라.


그래서 왠지 스프링에서도 밀고 있는 것 같은 thymeleaf를 써보기로 했다.
(react 등 client-side rendering 등은 일단 다음 기회에...)



3.1. 첫인상

뭐 특별한 것은 없었다. velocity engine을 ViewResolver로 사용하기 위해 만들었던 javaConfig 파일을 주석처리 하고 application.properties 몇 줄 수정하는 것으로 thymeleaf가 동작한다.

3.2 첫 난관

template layout을 만들 때 고생을 조금 했다.
html tag를 적극적으로 활용하다 보니,
'어떤 부분이 replace 되는 건가?' 하는 부분들이 직관적으로 보이지가 않았다.

웹브라우져에서도 본래 레이아웃을 확인 할 수 있는게 장점이라는데(?)
그렇게 하기 위해서는 각 페이지 레이아웃을 반복적으로? 사용해줘야 한다.
굳이 그럴 필요가 있나???

한편, thymeleaf를 좀 쉽게 사용하려면 `thymeleaf-layout-dialect` 이걸 활용할 필요가 있는 것 같다.

스프링부트 dependency에서 spring-boot-starter-thymeleaf을 포함하면 해당 패키지에 같이 포함되어 있다. - 다행이다 -


3.3 그 이후

그렇게 Velocity로 만들었던 폼들을 thymeleaf로 변환하고 보니,
뭐 딱히 템플릿 엔진의 기능을 많이 안써서 그런지 별 차이를 못 느끼겠다.

아무래도 html tag 형식을 빌려쓰고 있는 thymeleaf는 표현의 제약이 느껴진다.
마법의 태그(?)로 보이는 `th:block`과 `th:remove="tag"`를 잘 활용하는 방법 밖에는 없는 것 같다.


EDIT:
그런데 또 막상 쓰다보니 표현의 제약 그런거 별로 없더라.
어떻게 풀어서 쓰면 다 된다;;;

2016년 10월 11일 화요일

Builder 시리즈 - Biz PPurio SMS Builder


서비스 제공업체들이 준 API들 쓰기가 너무 귀찮고 복잡해서
builder pattern으로 만들었다. 시리즈 첫번째.

android만 봐도 클래스에 옵션이 많다 싶으면 builder를 제공하는데, 몇개 안보긴 했지만 제대로 된 example이나 builder를 제공하는게 별로 없는 것 같다.

이번에 만든 것은 bizPurio builder 이다.

하나는 SMS message builder.
property 마다 주석 달아놔서 좀 읽기 쉬울거라고 생각한다.

다른 하나는 실제 메세지를 보내는 class builder.
인라인 주석으로 되어 있는거 보기가 싫어서 마찬가지로 builder pattern으로 작업.

작업하면서 method chaining이 되도록 하는 방법을 알게된게 소득이랄까.

EDIT:
...purio가 아니라 [ppurio 뿌리오]였구나. sms를 뿌리오? 이런건가 보다;;
/**
* BizPurio 메시지 전송 class build
* @author by dotkebi@gmail.com on 2016-10-11.
*/
public class BizSenderBuilder {
private BizSend bizSend;
private String ip;
private int port;
private String userId;
private String password;
/**
* Console 에서 로그를 확인할 경우 설정
*/
private boolean logged = false;
/**
* 전송할 파일 경로 설정
* ex. FAX, PHONE, MMS 등
*/
private String filePath;
/**
* 블랙리스트 파일 경로 설정
*/
private String blackListPath;
/**
* 수신번호, 메세지 내용을 암호화 여부
*/
private boolean encryptMessage = false;
/**
* 첨부 파일 암호화 여부
*/
private boolean encryptFile = false;
/**
* 첨부 파일 전송 후 삭제 여부
* (T : 삭제, F : 남김)
*/
private boolean deleteFileAfterSend = false;
private BizSenderBuilder() {}
public static BizSenderBuilder init(String ip, int port, String userId, String password) {
BizSenderBuilder builder = new BizSenderBuilder();
builder.ip = ip;
builder.port = port;
builder.userId = userId;
builder.password = password;
return builder;
}
public String send(SendMsgEntity sendMsgEntity) {
bizSend = new BizSend();
bizSend.doBegin(ip, port, userId, password);
bizSend.setLogEnabled(logged);
if (!isEmpty(filePath)) {
bizSend.setFilePath(filePath);
}
if (!isEmpty(blackListPath)) {
bizSend.setFilePath(blackListPath);
}
try {
return bizSend.sendMsg(sendMsgEntity, encryptMessage, encryptFile, deleteFileAfterSend);
} catch (Exception e) {
e.printStackTrace();
} finally {
bizSend.doEnd();
}
return "";
}
/**
* PING-PONG
* 장시간 연결하여 메시지를 전송할 경우 연결이 끊어지지 않기 위해 실행
* 이전 호출 시점으로부터 30초 이상 지난 경우에만 PING 을 전송하도록 함수내 정의되어 있음
* 실행하지 않아도 메시지 전송에는 영향을 미치지 않으나 안전한 연결을 위해 주기적으로 실행하길 권장
*/
public boolean sendPing() {
return bizSend != null && bizSend.sendPing();
}
/**
* 리포트 재요청
* msgid 에 해당하는 메시지의 리포트가 시간이 지나도 오지 않는 경우 실행
* @param msgId 다우기술의 서버에서 정의한 message id
*/
public boolean reconfirmRepost(String msgId) {
return bizSend != null && bizSend.reconfirmReport(msgId);
}
private boolean isEmpty(String message) {
return message == null || "".equals(message);
}
/**
* setters
*/
public BizSenderBuilder setLogged(boolean logged) {
this.logged = logged;
return this;
}
public BizSenderBuilder setFilePath(String filePath) {
this.filePath = filePath;
return this;
}
public BizSenderBuilder setBlackListPath(String blackListPath) {
this.blackListPath = blackListPath;
return this;
}
public BizSenderBuilder setEncryptMessage(boolean encryptMessage) {
this.encryptMessage = encryptMessage;
return this;
}
public BizSenderBuilder setEncryptFile(boolean encryptFile) {
this.encryptFile = encryptFile;
return this;
}
public BizSenderBuilder setDeleteFileAfterSend(boolean deleteFileAfterSend) {
this.deleteFileAfterSend = deleteFileAfterSend;
return this;
}
}
/**
* Biz Purio 메시지 Builder
* @author by dotkebi@gmail.com on 2016-10-11.
*/
public class SMSMessageBuilder {
public enum MessageType {
SMS, WAP, FAX, PHONE, SMS_INBOUND, MMS
}
/*******************
* 필수
******************/
/**
* 데이터 id
*/
private String cmid;
/**
* 데이터 타입
*/
private MessageType messageType;
/**
* 받는 사람 전화 번호
*/
private String destPhone;
/**
* 보내는 사람 전화 번호
*/
private String sendPhone;
/**
* 데이터 내용
*/
private String messageBody;
/**
* (FAX/PHONE) 재시도 회수 (5~10분 간격: 최대 3회)
*/
private int retryCount;
/*******************
* Optional
******************/
/**
* 발송 (예약) 시간 (Unix Time, 정의하지 않을 경우 즉시 발송)
*/
private String sendTime = "";
/**
* 받는 사람 이름
*/
private String destName = "";
/**
* 보내는 사람 이름
*/
private String sendName = "";
/**
* (FAX/MMS) 제목, (SMS_INBOUND) 데이터 내용
*/
private String subject = "";
/**
* (WAP) URL 주소
*/
private String url = "";
/**
* (FAX) 표지 발송 옵션
*/
private int coverFlag = 0;
/**
* (PHONE) PHONE 실패 시 문자 전송 옵션
*/
private int smsFlag = 0;
/**
* (PHONE) 응답 받기 선택
*/
private int replyFlag = 0;
/**
* (PHONE/FAX/MMS) 파일 전송시 파일 이름
*/
private String faxFile = "";
/**
* (PHONE) 음성 시나리오 파일 이름
*/
private String vxmlFile = "";
private SMSMessageBuilder() {}
public static SMSMessageBuilder init(String senderNumber, String callNo, String msg) {
SMSMessageBuilder builder = new SMSMessageBuilder();
builder.cmid = new SimpleDateFormat("yyyyMMdd").format(new Date()) + "700";
builder.messageType = (msg.length() < 80) ? MessageType.SMS : MessageType.MMS;
builder.destPhone = callNo;
builder.sendPhone = senderNumber;
builder.messageBody = msg;
builder.retryCount = 0;
return builder;
}
public SendMsgEntity build() {
SendMsgEntity message = new SendMsgEntity();
message.setCMID(this.cmid);
message.setMSG_TYPE(this.messageType.ordinal());
message.setDEST_PHONE(this.destPhone);
message.setSEND_PHONE(this.sendPhone);
message.setMSG_BODY(this.messageBody);
message.setRETRY_CNT(this.retryCount);
if (!isEmpty(sendTime)) {
message.setSEND_TIME(sendTime);
}
if (!isEmpty(destName)) {
message.setDEST_NAME(destName);
}
if (!isEmpty(sendName)) {
message.setSEND_NAME(sendName);
}
if (!isEmpty(subject)) {
message.setSUBJECT(subject);
}
if (!isEmpty(url)) {
message.setWAP_URL(url);
}
if (!isEmpty(coverFlag)) {
message.setCOVER_FLAG(coverFlag);
}
if (!isEmpty(smsFlag)) {
message.setSMS_FLAG(smsFlag);
}
if (!isEmpty(replyFlag)) {
message.setREPLY_FLAG(replyFlag);
}
if (!isEmpty(faxFile)) {
message.setSEND_TIME(faxFile);
}
if (!isEmpty(vxmlFile)) {
message.setSEND_TIME(vxmlFile);
}
return message;
}
private boolean isEmpty(String message) {
return message == null || "".equals(message);
}
private boolean isEmpty(int message) {
return message == 0;
}
/**
* setters
*/
public SMSMessageBuilder setMessageType(MessageType messageType) {
this.messageType = messageType;
return this;
}
public SMSMessageBuilder setSendPhone(String sendPhone) {
this.sendPhone = sendPhone;
return this;
}
public SMSMessageBuilder setRetryCount(int retryCount) {
this.retryCount = retryCount;
return this;
}
public SMSMessageBuilder setSendTime(String sendTime) {
this.sendTime = sendTime;
return this;
}
public SMSMessageBuilder setDestName(String destName) {
this.destName = destName;
return this;
}
public SMSMessageBuilder setSendName(String sendName) {
this.sendName = sendName;
return this;
}
public SMSMessageBuilder setSubject(String subject) {
this.subject = subject;
return this;
}
public SMSMessageBuilder setUrl(String url) {
this.url = url;
return this;
}
public SMSMessageBuilder setCoverFlag(int coverFlag) {
this.coverFlag = coverFlag;
return this;
}
public SMSMessageBuilder setSmsFlag(int smsFlag) {
this.smsFlag = smsFlag;
return this;
}
public SMSMessageBuilder setReplyFlag(int replyFlag) {
this.replyFlag = replyFlag;
return this;
}
public SMSMessageBuilder setFaxFile(String faxFile) {
this.faxFile = faxFile;
return this;
}
public SMSMessageBuilder setVxmlFile(String vxmlFile) {
this.vxmlFile = vxmlFile;
return this;
}
}