A Mendix SDK Primer - 2부 | Mendix

메인 컨텐츠로 가기

A Mendix SDK Primer - 2부

Mendix SDK Primer Pt 2 메인 이미지는 중앙에 3D 회색 상자가 있는 보라색과 산호색 배경을 보여줍니다. Mendix 한 면에는 로고가 있고, 다른 한 면에는 가짜 코드가 있고, 세 번째 면에는 서로 연결된 요소의 다이어그램이 있습니다.

이것은 짧은 시리즈의 두 번째 블로그 게시물입니다. Mendix 플랫폼/모델 SDK를 사용하면 다음을 사용하지 않고도 앱 모델에 대한 프로그래밍 방식 액세스를 제공할 수 있습니다. Mendix 스튜디오 또는 Mendix 스튜디오 프로.

앱 생성 및 모델 구축

. 첫 번째 SDK 블로그 게시물 타입스크립트 스크립트와 NodeJS를 사용하여 액세스하는 방법을 보여드렸습니다. Mendix 앱 모델을 만들고 모델에서 JavaScript 스크립트로 정보를 내보냅니다. 이제 새 것을 만들겠습니다. Mendix Platform/Model SDK 스크립트를 사용하여 앱을 만들고, 앱에 새 모듈을 빌드하고, 해당 모듈에 일부 요소를 배치합니다.

A Mendix SDK Primer - 2부_빈 화면이 있는 컴퓨터 이미지, 시작할 빈 슬레이트를 나타냄

새로운 앱

블로그의 경우 사용 사례는 매우 간단해야 하므로 매우 단순한 주문 입력 시나리오를 모방한 앱을 만들겠습니다. 앱에는 다음이 포함됩니다.

  • 주문 ID에 대한 자동 번호, 주문 값, 주문 상태, 고객 이름을 갖는 주문 엔터티로, 수신된 주문의 문서(PDF 등)도 보관할 수 있습니다.
  • 주문 라인 ID, 제품 이름, 라인 값을 갖는 주문 라인 엔터티입니다.
  • 주문 및 주문 라인 엔터티 간의 계단식 삭제 연결입니다.
  • 사용자가 입력한 최대값과 최소값 사이의 주문을 검색하는 데 사용될 수 있는 비영구적 엔터티입니다.
  • 주문 상태를 나타내는 열거형입니다.
  • 관리자 역할과 일반 사용자 역할로 구성된 프로덕션 수준 보안. 영구 엔터티를 관리자가 쓸 수 있게 했지만 일반 사용자는 읽기 전용으로 설정하여 이것이 어떻게 이루어지는지 보여드렸습니다.

스크립트는 이 모든 것을 처음부터 빌드하고 다음을 제어할 수도 있습니다. Mendix 생성된 앱의 기반으로 사용되는 버전 및 시작 앱 템플릿입니다.

이후의 블로그 게시물에서는 이 앱을 기반으로 더 많은 내용을 다룰 수 있을 것으로 기대합니다.

시작하려면: 모든 활동은 main()이라는 비동기 함수에서 발생합니다. 이를 통해 await 수정자를 사용하여 코드를 간소화할 수 있습니다.

앱 만들기

이것은 간단하며 앱의 이름과 선택적 요약(설명) 및 템플릿 ID만 있으면 됩니다. 템플릿 ID를 지정하지 않으면 최신 사용 가능한 템플릿의 기본 템플릿이 사용됩니다. Mendix 버전이 사용됩니다.

나는 Blank Web App 템플릿을 사용할 것입니다. Mendix 9.18.1.

앱에 사용하려는 템플릿의 템플릿 ID는 다음을 확인하여 찾을 수 있습니다. Mendix 마켓플레이스 및 "템플릿" 옵션 선택:

A Mendix SDK Primer - 2부_사용하려는 템플릿을 찾으려면 다음을 참조하세요. Mendix 온라인마켓

원하는 템플릿을 클릭한 다음 템플릿 페이지 내의 "릴리스" 탭을 클릭합니다.

A Mendix SDK Primer - 2부_템플릿 페이지를 보여주는 릴리스 탭 이미지

개별 릴리스를 열면 각각에 대한 세부 정보를 찾을 수 있습니다. 여기에는 "Mendix 의도된 버전”입니다. 모든 Mendix 버전이 표시됩니다. 표시되지 않은 버전을 사용해야 하는 경우 원하는 버전보다 최신 프레임워크 버전을 선택한 다음 앱이 빌드된 후 Studio Pro에서 앱을 업그레이드합니다.

A Mendix SDK Primer - 2부_마켓플레이스 템플릿 릴리스 세부 정보

사용하려는 릴리스에 대해 제공된 UUID를 복사합니다. 이는 createNewApp 명령문에 삽입할 수 있는 템플릿 ID입니다.

    import { MendixPlatformClient } from "mendixplatformsdk";
    import { projects, security, domainmodels, enumerations, texts } from 'mendixmodelsdk';
    
    main().catch(console.error);
    
    
    async function main()
    {
        const client = new MendixPlatformClient();
    
        // Create the app
        const app = await client.createNewApp("SDKTestApp", {summary: "Test App created using the Mendix SDKs", templateId: "5cdf7b82-e902-4b8d-b0dd-bdd939c9e82f"});
    
        console.log("App created with App ID: " + app.appId);

다음으로 앱의 작업 사본을 만들고 모델을 열어 업데이트합니다.

        // Open a working copy
        const workingCopy = await app.createTemporaryWorkingCopy("trunk");
        const model = await workingCopy.openModel();

모듈 설정

다음으로 새 앱 모듈을 만들고 선택한 이름을 지정한 다음 모듈의 보안을 초기화합니다. 이 앱에서는 관리자 액세스와 일반 사용자 액세스의 두 가지 역할로 작업합니다. 제가 설정한 방식은 SDK에서 보안을 설정하는 방법을 보여주려고 하는 것이므로 그다지 현실적이지 않습니다. 관리자는 엔터티에 대한 쓰기 액세스 권한이 있고 일반 사용자는 읽기 액세스 권한만 있습니다(한 가지 예외가 있음).

        // Create a new module
        const module = projects.Module.createIn(model.allProjects()[0]);
        module.name = "SDKModule";
    
        // Set up module security
        const moduleSecurity = security.ModuleSecurity.createIn(module);
        const adminRole = security.ModuleRole.createIn(moduleSecurity);
        adminRole.name = "Admin";
        const userRole = security.ModuleRole.createIn(moduleSecurity);
        userRole.name = "User";

마지막으로 모듈에 대한 도메인 모델을 만들어야 하며 이제 모듈을 채울 준비가 되었습니다.

S        // Create the module domain model
        const domainModel = domainmodels.DomainModel.createIn(module);

열거형을 만듭니다

주문 엔터티의 "주문 상태"는 열거형이어야 하므로 먼저 이를 생성하고 가능한 네 가지 값을 지정하겠습니다. 신제품, 보류, 진행 중진행완료간단하게 설명하기 위해 각 값에 대해 미국 영어 캡션만 제공합니다.

        // Make the Order Status enumeration with its values
        const statusEnumeration = enumerations.Enumeration.createIn(module);
        statusEnumeration.name = "ENUM_Status";
    
        const statusEnumerationValue1 = enumerations.EnumerationValue.createIn(statusEnumeration);
        statusEnumerationValue1.name = "_New";
        const statusEnumerationCaption1 = texts.Text.createInEnumerationValueUnderCaption(statusEnumerationValue1);
        const statusEnumerationTranslation1 = texts.Translation.create(model);
        statusEnumerationTranslation1.text = "New";
        statusEnumerationTranslation1.languageCode = "en_US";
        statusEnumerationCaption1.translations.push(statusEnumerationTranslation1);
    
        const statusEnumerationValue2 = enumerations.EnumerationValue.createIn(statusEnumeration);
        statusEnumerationValue2.name = "Hold";
        const statusEnumerationCaption2 = texts.Text.createInEnumerationValueUnderCaption(statusEnumerationValue2);
        const statusEnumerationTranslation2 = texts.Translation.create(model);
        statusEnumerationTranslation2.text = "On Hold";
        statusEnumerationTranslation2.languageCode = "en_US";
        statusEnumerationCaption2.translations.push(statusEnumerationTranslation2);
       
        const statusEnumerationValue3 = enumerations.EnumerationValue.createIn(statusEnumeration);
        statusEnumerationValue3.name = "Progress";
        const statusEnumerationCaption3 = texts.Text.createInEnumerationValueUnderCaption(statusEnumerationValue3);
        const statusEnumerationTranslation3 = texts.Translation.create(model);
        statusEnumerationTranslation3.text = "In Progress";
        statusEnumerationTranslation3.languageCode = "en_US";
        statusEnumerationCaption3.translations.push(statusEnumerationTranslation3);
       
        const statusEnumerationValue4 = enumerations.EnumerationValue.createIn(statusEnumeration);
        statusEnumerationValue4.name = "Completed";
        const statusEnumerationCaption4 = texts.Text.createInEnumerationValueUnderCaption(statusEnumerationValue4);
        const statusEnumerationTranslation4 = texts.Translation.create(model);
        statusEnumerationTranslation4.text = "Completed";
        statusEnumerationTranslation4.languageCode = "en_US";
        statusEnumerationCaption4.translations.push(statusEnumerationTranslation4);

좋아요, 꽤 간단했으니 조금 더 복잡한 것으로 넘어가보죠.

엔터티 생성

이 작업을 진행하면서 여기 있는 코드와 Studio Pro 내에서 동일한 작업을 수행하는 것 사이에 명확한 대응 관계가 있음을 알 수 있을 것입니다.

주문 라인 엔터티부터 시작해 보겠습니다. 엔터티 생성은 간단합니다. 위치는 Studio Pro 도메인 모델 다이어그램에서 엔터티가 차지하는 위치를 나타냅니다. 여기서 x는 다이어그램 왼쪽에서 가로로 측정한 다이어그램 지점의 수평 위치를 지정하고, y는 다이어그램 상단에서 아래로 측정한 다이어그램 지점의 수직 위치를 지정합니다. 둘 다 엔터티 상자의 왼쪽 상단 모서리를 반영합니다.

        // Create the Order Line Entity and attributes
        const orderLineEntity = domainmodels.Entity.createIn(domainModel);
        orderLineEntity.name = "OrderLine";
        orderLineEntity.location = { x: 100, y: 100};
        orderLineEntity.documentation = "The Order Line entity created using the Model SDK";

처음 두 속성도 간단합니다. 하나는 정수이고 다른 하나는 소수입니다.

        const orderLineIdAttributeType = domainmodels.IntegerAttributeType.create(model);
        const orderLineIdAttributeDefault = domainmodels.StoredValue.create(model);
        orderLineIdAttributeDefault.defaultValue = "";
        const orderLineIdAttribute = domainmodels.Attribute.createIn(orderLineEntity);
        orderLineIdAttribute.name = "OrderLineId";
        orderLineIdAttribute.type = orderLineIdAttributeType;
        orderLineIdAttribute.value = orderLineIdAttributeDefault;
        orderLineIdAttribute.documentation = "The Id of this Order Line unique within an order created using the Model SDK";
    
        const orderLineValueAttributeType = domainmodels.DecimalAttributeType.create(model);
        const orderLineValueAttributeDefault = domainmodels.StoredValue.create(model);
        orderLineValueAttributeDefault.defaultValue = "";
        const orderLineValueAttribute = domainmodels.Attribute.createIn(orderLineEntity);
        orderLineValueAttribute.name = "OrderLineValue";
        orderLineValueAttribute.type = orderLineValueAttributeType;
        orderLineValueAttribute.value = orderLineValueAttributeDefault;
        orderLineValueAttribute.documentation = "The Value of this Order Line created using the Model SDK";

세 번째 속성에는 몇 가지 추가 사항이 있습니다.

  • 속성은 문자열이며 길이(300)가 지정되어 있습니다.
  • 속성에는 비어 있지 않은 기본값인 "행복한 날들"이 있습니다.
  • 속성에는 필수 검증 규칙이 적용되어 있습니다. 여기에는 오류 메시지가 정의되어야 합니다. 다시 말하지만, 단순성을 위해 미국 영어를 사용합니다.
        const orderLineProductNameAttributeType = domainmodels.StringAttributeType.create(model);
        orderLineProductNameAttributeType.length = 300;
        const orderLineProductNameAttributeDefault = domainmodels.StoredValue.create(model);
        orderLineProductNameAttributeDefault.defaultValue = "Happy Days";
        const orderLineProductNameAttribute = domainmodels.Attribute.createIn(orderLineEntity);
        orderLineProductNameAttribute.name = "ProductName";
        orderLineProductNameAttribute.type = orderLineProductNameAttributeType;
        orderLineProductNameAttribute.value = orderLineProductNameAttributeDefault;
        orderLineProductNameAttribute.documentation = "The name of the Product the subject of this Order Line created using the Model SDK";
        const orderLineEntityValidationRule1 = domainmodels.ValidationRule.createIn(orderLineEntity);
        orderLineEntityValidationRule1.attribute = orderLineProductNameAttribute;
        domainmodels.RequiredRuleInfo.createIn(orderLineEntityValidationRule1);
        const orderLineEntityValidationRuleText = texts.Text.createInValidationRuleUnderErrorMessage(orderLineEntityValidationRule1);
        const orderLineEntityValidationRuleTranslation = texts.Translation.create(model);
        orderLineEntityValidationRuleTranslation.text = "A Product Name must be entered";
        orderLineEntityValidationRuleTranslation.languageCode = "en_US";
        orderLineEntityValidationRuleText.translations.push(orderLineEntityValidationRuleTranslation);

다음으로, 주문 엔터티를 만들겠습니다. 이것은 System.FileDocument의 특수화이므로 주문 라인과 약간 다릅니다. 제가 언급했듯이 SDK 블로그 1부, 시스템 모듈 요소를 사용하는 것은 보안상의 이유로 SDK 스크립트에서 직접 참조할 수 없기 때문에 약간 어색합니다. 그래서 우리는 해결책을 사용합니다. 일반화 엔티티가 시스템 모듈에 없다면 직접 참조할 수 있습니다.

        // Create the Order Entity and its attributes
        const orderEntity = domainmodels.Entity.createIn(domainModel);
        orderEntity.name = "Order";
        orderEntity.location = { x: 500, y: 100};
        orderEntity.documentation = "The Order Entity specializing from System.FileDocument created using the Model SDK";
        const orderEntityGeneralization = domainmodels.Generalization.createIn(orderEntity);
        (orderEntityGeneralization as any)["__generalization"].updateWithRawValue("System.FileDocument");

지금으로서는 order 엔터티의 속성에 별다른 특별한 작업을 하지 않으므로 코드는 간단하지만, 길이가 지정된 문자열 속성(100)이 있고, AutoNumber OrderId 속성에는 기본 시작 값이 정의되어 있으며('1'로 설정), 위에서 만든 열거형을 사용하는 OrderStatus에는 기본값이 설정되어 있습니다('_New').

        const orderIdAttributeType = domainmodels.AutoNumberAttributeType.create(model);
        const orderIdAttributeDefault = domainmodels.StoredValue.create(model);
        orderIdAttributeDefault.defaultValue = "1";
        const orderIdAttribute = domainmodels.Attribute.createIn(orderEntity);
        orderIdAttribute.name = "OrderId";
        orderIdAttribute.type = orderIdAttributeType;
        orderIdAttribute.value = orderIdAttributeDefault;
        orderIdAttribute.documentation = "The Id of this Order Id unique accross the system created using the Model SDK";
    
        const orderValueAttributeType = domainmodels.DecimalAttributeType.create(model);
        const orderValueAttributeDefault = domainmodels.StoredValue.create(model);
        orderValueAttributeDefault.defaultValue = "";
        const orderValueAttribute = domainmodels.Attribute.createIn(orderEntity);
        orderValueAttribute.name = "OrderValue";
        orderValueAttribute.type = orderValueAttributeType;
        orderValueAttribute.value = orderValueAttributeDefault;
        orderValueAttribute.documentation = "The Value of this Order created using the Model SDK";
    
        const customerNameAttributeType = domainmodels.StringAttributeType.create(model);
        customerNameAttributeType.length = 100;
        const customerNameAttributeDefault = domainmodels.StoredValue.create(model);
        customerNameAttributeDefault.defaultValue = "";
        const customerNameAttribute = domainmodels.Attribute.createIn(orderEntity);
        customerNameAttribute.name = "CustomerName";
        customerNameAttribute.type = customerNameAttributeType;
        customerNameAttribute.value = customerNameAttributeDefault;
        customerNameAttribute.documentation = "The name of the Customer who raised this Order created using the Model SDK";
    
        const orderStatusAttributeType = domainmodels.EnumerationAttributeType.create(model);
        orderStatusAttributeType.enumeration = statusEnumeration;
        const orderStatusAttributeDefault = domainmodels.StoredValue.create(model);
        orderStatusAttributeDefault.defaultValue = "_New";
        const orderStatusAttribute = domainmodels.Attribute.createIn(orderEntity);
        orderStatusAttribute.name = "OrderStatus";
        orderStatusAttribute.type = orderStatusAttributeType;
        orderStatusAttribute.value = orderStatusAttributeDefault;
        orderStatusAttribute.documentation = "The status of this Order created using the Model SDK";

비지속성 OrderSearch 엔터티와 그 속성을 만듭니다. 비지속성은 ​​간단한 플래그를 사용하여 설정됩니다.

        // Create the OrderSearch non-persistent Entity
        const orderSearchEntity = domainmodels.Entity.createIn(domainModel);
        orderSearchEntity.name = "OrderSearch";
        orderSearchEntity.location = { x: 900, y: 100};
        orderSearchEntity.documentation = "A non-persistent entity used for searching Orders created using the Model SDK";
        const orderSearchEntityNoGeneralization = domainmodels.NoGeneralization.createIn(orderSearchEntity);
        orderSearchEntityNoGeneralization.persistable = false;
    
        const orderSearchMinimumValueAttributeType = domainmodels.DecimalAttributeType.create(model);
        const orderSearchMinimumValueDefaultValue = domainmodels.StoredValue.create(model);
        orderSearchMinimumValueDefaultValue.defaultValue = "";
        const orderSearchMiniumuValueAttribute = domainmodels.Attribute.createIn(orderSearchEntity);
        orderSearchMiniumuValueAttribute.name = "MinimumValue";
        orderSearchMiniumuValueAttribute.type = orderSearchMinimumValueAttributeType;
        orderSearchMiniumuValueAttribute.value = orderSearchMinimumValueDefaultValue;
        orderSearchMiniumuValueAttribute.documentation = "The search order value minimum value created using the Model SDK";
    
        const orderSearchMaximumValueAttributeType = domainmodels.DecimalAttributeType.create(model);
        const orderSearchMaximumValueDefaultValue = domainmodels.StoredValue.create(model);
        orderSearchMaximumValueDefaultValue.defaultValue = "";
        const orderSearchMaxiumuValueAttribute = domainmodels.Attribute.createIn(orderSearchEntity);
        orderSearchMaxiumuValueAttribute.name = "MaximumValue";
        orderSearchMaxiumuValueAttribute.type = orderSearchMaximumValueAttributeType;
        orderSearchMaxiumuValueAttribute.value = orderSearchMaximumValueDefaultValue;
        orderSearchMaxiumuValueAttribute.documentation = "The search order value maximum value created using the Model SDK";

마지막으로 주문 및 주문 라인 엔터티 간의 연결을 만들어야 합니다. 아이연결 그리고 부모연결 설정은 엔터티의 설정과 유사합니다. 위치 값은 있지만 다이어그램의 절대 위치 대신 백분율을 나타냅니다. 그래서 x 다이어그램에서 엔터티의 너비를 따라 얼마나 떨어져 있는지를 나타냅니다. y 다이어그램에서 엔터티 측면의 아래쪽을 나타냅니다. 이를 사용하면 연결을 나타내는 선이 엔터티 상자의 어느 지점에서든 끝나도록 설정할 수 있습니다.

연결이 생성되면 삭제 동작을 설정하여 주문 객체가 삭제되면 연결된 OrderLine 객체도 삭제되도록 합니다.

        // Create the association between the OrderLine and Order Entities
        const association = domainmodels.Association.createIn(domainModel);
        association.name = "OrderLine_Order"; 
        association.child = orderEntity;
        association.parent = orderLineEntity;
        association.type = domainmodels.AssociationType.Reference;
        association.owner = domainmodels.AssociationOwner.Default;
        association.childConnection = { x: 0, y: 50};
        association.parentConnection = { x: 100, y: 50};
        association.documentation = "Association created using the Model SDK";
        const associationDeleteBehavior = domainmodels.AssociationDeleteBehavior.createIn(association);
        associationDeleteBehavior.childDeleteBehavior = domainmodels.DeletingBehavior.DeleteMeAndReferences;
        association.deleteBehavior = associationDeleteBehavior;

이는 모두 다음과 같은 도메인 모델 다이어그램으로 표현되어야 합니다.

A Mendix SDK Primer - 2부_도메인 모델 다이어그램

엔터티 보안 설정

이제 우리는 우리가 만든 각 관리자 및 사용자 역할에 대한 보안을 제어하기 위한 설정 목록을 실행해야 합니다. 각 속성과 연관은 별도로 설정해야 하며 역할이 다르면 각 역할도 별도로 설정해야 합니다. 이것은 비교적 간단하지만 다소 오래 걸립니다.

또한 시스템 모듈에 직접 접근할 수 없으므로 이전과 비슷한 해결 방법을 사용하여 System.FileDocument에서 상속된 특성에 대한 설정을 정의해야 합니다.

        // Set up the security on the new module's Entities
        // Security settings for Order Line Entity for Admin user
        const orderLineEntityAdminAccessRule = domainmodels.AccessRule.createInEntityUnderAccessRules(orderLineEntity);
        orderLineEntityAdminAccessRule.allowCreate = true;
        orderLineEntityAdminAccessRule.allowDelete = true;
        orderLineEntityAdminAccessRule.defaultMemberAccessRights = domainmodels.MemberAccessRights.ReadWrite;
        orderLineEntityAdminAccessRule.moduleRoles.push(adminRole);
        
        const orderLineEntityAdminAccessOrderLineId = domainmodels.MemberAccess.createIn(orderLineEntityAdminAccessRule);
        orderLineEntityAdminAccessOrderLineId.attribute = orderLineIdAttribute;
        orderLineEntityAdminAccessOrderLineId.accessRights = domainmodels.MemberAccessRights.ReadWrite;
    
        const orderLineEntityAdminAccessProductName = domainmodels.MemberAccess.createIn(orderLineEntityAdminAccessRule);
        orderLineEntityAdminAccessProductName.attribute = orderLineProductNameAttribute;
        orderLineEntityAdminAccessProductName.accessRights = domainmodels.MemberAccessRights.ReadWrite;
    
        const orderLineEntityAdminAccessOrderLineValue = domainmodels.MemberAccess.createIn(orderLineEntityAdminAccessRule);
        orderLineEntityAdminAccessOrderLineValue.attribute = orderLineValueAttribute;
        orderLineEntityAdminAccessOrderLineValue.accessRights = domainmodels.MemberAccessRights.ReadWrite;
    
        const orderLineEntityAdminAccessAssociation = domainmodels.MemberAccess.createIn(orderLineEntityAdminAccessRule);
        orderLineEntityAdminAccessAssociation.association = association;
        orderLineEntityAdminAccessAssociation.accessRights = domainmodels.MemberAccessRights.ReadWrite;
    
        // Security settings for Order Line Entity for regular user
        const orderLineEntityUserAccessRule = domainmodels.AccessRule.createInEntityUnderAccessRules(orderLineEntity);
        orderLineEntityUserAccessRule.allowCreate = false;
        orderLineEntityUserAccessRule.allowDelete = false;
        orderLineEntityUserAccessRule.defaultMemberAccessRights = domainmodels.MemberAccessRights.ReadOnly;
        orderLineEntityUserAccessRule.moduleRoles.push(userRole);
        
        const orderLineEntityUserAccessOrderLineId = domainmodels.MemberAccess.createIn(orderLineEntityUserAccessRule);
        orderLineEntityUserAccessOrderLineId.attribute = orderLineIdAttribute;
        orderLineEntityUserAccessOrderLineId.accessRights = domainmodels.MemberAccessRights.ReadOnly;
    
        const orderLineEntityUserAccessProductName = domainmodels.MemberAccess.createIn(orderLineEntityUserAccessRule);
        orderLineEntityUserAccessProductName.attribute = orderLineProductNameAttribute;
        orderLineEntityUserAccessProductName.accessRights = domainmodels.MemberAccessRights.ReadOnly;
    
        const orderLineEntityUserAccessOrderLineValue = domainmodels.MemberAccess.createIn(orderLineEntityUserAccessRule);
        orderLineEntityUserAccessOrderLineValue.attribute = orderLineValueAttribute;
        orderLineEntityUserAccessOrderLineValue.accessRights = domainmodels.MemberAccessRights.ReadOnly;
    
        const orderLineEntityUserAccessAssociation = domainmodels.MemberAccess.createIn(orderLineEntityUserAccessRule);
        orderLineEntityUserAccessAssociation.association = association;
        orderLineEntityUserAccessAssociation.accessRights = domainmodels.MemberAccessRights.ReadOnly;
    
        // Security settings for Order Entity for Admin user
        const orderEntityAdminAccessRule = domainmodels.AccessRule.createInEntityUnderAccessRules(orderEntity);
        orderEntityAdminAccessRule.allowCreate = true;
        orderEntityAdminAccessRule.allowDelete = true;
        orderEntityAdminAccessRule.defaultMemberAccessRights = domainmodels.MemberAccessRights.ReadWrite;
        orderEntityAdminAccessRule.moduleRoles.push(adminRole);
        
        const orderEntityAdminAccessOrderId = domainmodels.MemberAccess.createIn(orderEntityAdminAccessRule);
        orderEntityAdminAccessOrderId.attribute = orderIdAttribute;
        orderEntityAdminAccessOrderId.accessRights = domainmodels.MemberAccessRights.ReadOnly;
    
        const orderEntityAdminAccessOrderValue = domainmodels.MemberAccess.createIn(orderEntityAdminAccessRule);
        orderEntityAdminAccessOrderValue.attribute = orderValueAttribute;
        orderEntityAdminAccessOrderValue.accessRights = domainmodels.MemberAccessRights.ReadWrite;
    
        const orderEntityAdminAccessCustomerName = domainmodels.MemberAccess.createIn(orderEntityAdminAccessRule);
        orderEntityAdminAccessCustomerName.attribute = customerNameAttribute;
        orderEntityAdminAccessCustomerName.accessRights = domainmodels.MemberAccessRights.ReadWrite;
    
        const orderEntityAdminAccessOrderStatus = domainmodels.MemberAccess.createIn(orderEntityAdminAccessRule);
        orderEntityAdminAccessOrderStatus.attribute = orderStatusAttribute;
        orderEntityAdminAccessOrderStatus.accessRights = domainmodels.MemberAccessRights.ReadWrite;
    
        const orderEntityAdminAccessName = domainmodels.MemberAccess.createIn(orderEntityAdminAccessRule);
        (orderEntityAdminAccessName as any)["__attribute"].updateWithRawValue("System.FileDocument.Name");
        orderEntityAdminAccessName.accessRights = domainmodels.MemberAccessRights.ReadWrite;
    
        const orderEntityAdminAccessFileId = domainmodels.MemberAccess.createIn(orderEntityAdminAccessRule);
        (orderEntityAdminAccessFileId as any)["__attribute"].updateWithRawValue("System.FileDocument.FileID");
        orderEntityAdminAccessFileId.accessRights = domainmodels.MemberAccessRights.ReadOnly;
    
        const orderEntityAdminAccessDeleteAfterDownload = domainmodels.MemberAccess.createIn(orderEntityAdminAccessRule);
        (orderEntityAdminAccessDeleteAfterDownload as any)["__attribute"].updateWithRawValue("System.FileDocument.DeleteAfterDownload");
        orderEntityAdminAccessDeleteAfterDownload.accessRights = domainmodels.MemberAccessRights.ReadWrite;
    
        const orderEntityAdminAccessContents = domainmodels.MemberAccess.createIn(orderEntityAdminAccessRule);
        (orderEntityAdminAccessContents as any)["__attribute"].updateWithRawValue("System.FileDocument.Contents");
        orderEntityAdminAccessContents.accessRights = domainmodels.MemberAccessRights.ReadWrite;
    
        const orderEntityAdminAccessHasContents = domainmodels.MemberAccess.createIn(orderEntityAdminAccessRule);
        (orderEntityAdminAccessHasContents as any)["__attribute"].updateWithRawValue("System.FileDocument.HasContents");
        orderEntityAdminAccessHasContents.accessRights = domainmodels.MemberAccessRights.ReadOnly;
    
        const orderEntityAdminAccessSize = domainmodels.MemberAccess.createIn(orderEntityAdminAccessRule);
        (orderEntityAdminAccessSize as any)["__attribute"].updateWithRawValue("System.FileDocument.Size");
        orderEntityAdminAccessSize.accessRights = domainmodels.MemberAccessRights.ReadOnly;
    
        // Security settings for Order Entity for regular user
        const orderEntityUserAccessRule = domainmodels.AccessRule.createInEntityUnderAccessRules(orderEntity);
        orderEntityUserAccessRule.allowCreate = false;
        orderEntityUserAccessRule.allowDelete = false;
        orderEntityUserAccessRule.defaultMemberAccessRights = domainmodels.MemberAccessRights.ReadOnly;
        orderEntityUserAccessRule.moduleRoles.push(userRole);
        
        const orderEntityUserAccessOrderId = domainmodels.MemberAccess.createIn(orderEntityUserAccessRule);
        orderEntityUserAccessOrderId.attribute = orderIdAttribute;
        orderEntityUserAccessOrderId.accessRights = domainmodels.MemberAccessRights.ReadOnly;
    
        const orderEntityUserAccessOrderValue = domainmodels.MemberAccess.createIn(orderEntityUserAccessRule);
        orderEntityUserAccessOrderValue.attribute = orderValueAttribute;
        orderEntityUserAccessOrderValue.accessRights = domainmodels.MemberAccessRights.ReadOnly;
    
        const orderEntityUserAccesCustomerName = domainmodels.MemberAccess.createIn(orderEntityUserAccessRule);
        orderEntityUserAccesCustomerName.attribute = customerNameAttribute;
        orderEntityUserAccesCustomerName.accessRights = domainmodels.MemberAccessRights.ReadOnly;
    
        const orderEntityUserAccesOrderStatus = domainmodels.MemberAccess.createIn(orderEntityUserAccessRule);
        orderEntityUserAccesOrderStatus.attribute = orderStatusAttribute;
        orderEntityUserAccesOrderStatus.accessRights = domainmodels.MemberAccessRights.ReadOnly;
    
        const orderEntityUserAccessName = domainmodels.MemberAccess.createIn(orderEntityUserAccessRule);
        (orderEntityUserAccessName as any)["__attribute"].updateWithRawValue("System.FileDocument.Name");
        orderEntityUserAccessName.accessRights = domainmodels.MemberAccessRights.ReadOnly;
    
        const orderEntityUserAccessFileId = domainmodels.MemberAccess.createIn(orderEntityUserAccessRule);
        (orderEntityUserAccessFileId as any)["__attribute"].updateWithRawValue("System.FileDocument.FileID");
        orderEntityUserAccessFileId.accessRights = domainmodels.MemberAccessRights.ReadOnly;
    
        const orderEntityUserAccessDeleteAfterDownload = domainmodels.MemberAccess.createIn(orderEntityUserAccessRule);
        (orderEntityUserAccessDeleteAfterDownload as any)["__attribute"].updateWithRawValue("System.FileDocument.DeleteAfterDownload");
        orderEntityUserAccessDeleteAfterDownload.accessRights = domainmodels.MemberAccessRights.ReadOnly;
    
        const orderEntityUserAccessContents = domainmodels.MemberAccess.createIn(orderEntityUserAccessRule);
        (orderEntityUserAccessContents as any)["__attribute"].updateWithRawValue("System.FileDocument.Contents");
        orderEntityUserAccessContents.accessRights = domainmodels.MemberAccessRights.ReadOnly;
    
        const orderEntityUserAccessHasContents = domainmodels.MemberAccess.createIn(orderEntityUserAccessRule);
        (orderEntityUserAccessHasContents as any)["__attribute"].updateWithRawValue("System.FileDocument.HasContents");
        orderEntityUserAccessHasContents.accessRights = domainmodels.MemberAccessRights.ReadOnly;
    
        const orderEntityUserAccessSize = domainmodels.MemberAccess.createIn(orderEntityUserAccessRule);
        (orderEntityUserAccessSize as any)["__attribute"].updateWithRawValue("System.FileDocument.Size");
        orderEntityUserAccessSize.accessRights = domainmodels.MemberAccessRights.ReadOnly;
    
        // Security settings for OrderSearch Entity for both Admin and regular users
        const orderSearchEntityUserAccessRule = domainmodels.AccessRule.createInEntityUnderAccessRules(orderSearchEntity);
        orderSearchEntityUserAccessRule.allowCreate = true;
        orderSearchEntityUserAccessRule.allowDelete = true;
        orderSearchEntityUserAccessRule.defaultMemberAccessRights = domainmodels.MemberAccessRights.ReadWrite;
        orderSearchEntityUserAccessRule.moduleRoles.push(adminRole);
        orderSearchEntityUserAccessRule.moduleRoles.push(userRole);
        
        const orderSearchEntityUserAccessMinimumValue = domainmodels.MemberAccess.createIn(orderSearchEntityUserAccessRule);
        orderSearchEntityUserAccessMinimumValue.attribute = orderSearchMiniumuValueAttribute;
        orderSearchEntityUserAccessMinimumValue.accessRights = domainmodels.MemberAccessRights.ReadWrite;
    
        const orderSearchEntityUserAccessMaximumValue = domainmodels.MemberAccess.createIn(orderSearchEntityUserAccessRule);
        orderSearchEntityUserAccessMaximumValue.attribute = orderSearchMaxiumuValueAttribute;
        orderSearchEntityUserAccessMaximumValue.accessRights = domainmodels.MemberAccessRights.ReadWrite;e

완성

이제 거의 끝났습니다! 다음으로, 프로젝트를 "production"(모든 것을 확인) 보안 수준으로 설정하고 앞서 생성한 모듈 역할을 가져와 프로젝트 역할에 매핑합니다.

그런 다음 변경 사항을 플러시하고 커밋합니다.

        // Set up the project level security roles. Set Production level security and put the module roles into the project roles
        const projectSecurity = await model.allProjectSecurities()[0].load();
        projectSecurity.securityLevel = security.SecurityLevel.CheckEverything;
        projectSecurity.checkSecurity = true;
    
        const adminRoleName = projectSecurity.adminUserRoleName;
        const projectAdminRole = projectSecurity.userRoles.find(t => t.name === adminRoleName);
        const projectUserRole = projectSecurity.userRoles.find(t => t.name === "User");
    
        if (projectAdminRole != undefined)
            projectAdminRole.moduleRoles.push(adminRole);
        if (projectUserRole != undefined)
            projectUserRole.moduleRoles.push(userRole);
    
        console.log("Committing changes to the repository");
    
        await model.flushChanges();
        await workingCopy.commitToRepository("trunk", {commitMessage: "App created and modified by my Create App Model SDK script"});
    
    }

따라서 이 스크립트를 NodeJS 초기화 폴더에 저장하면(참조) SDK 블로그 1 그 방법에 대해서는 TypeScript 파일(나는 'createapp.ts'라고 불렀습니다)로 이제 TypeScript를 JavaScript로 컴파일할 수 있습니다. TSC 그리고 Node를 사용하여 생성된 JavaScript를 실행합니다.

이 스크립트를 실행하면 이제 추가된 모듈과 채워진 도메인 모델이 있는 앱이 생성되었을 것입니다. 좋아요, 아직은 별로 하지 않습니다. 이 블로그 시리즈의 세 번째 부분에서 뼈대에 살을 붙이기 시작하겠습니다.

A Mendix SDK Primer - 2부_빨간색 화면과 해적기가 있는 노트북 이미지, 무언가가 제대로 작동하지 않음을 나타냄

오류

SDK를 사용하여 앱을 업데이트할 때의 경험에서 나온 몇 가지 경고의 말씀. SDK 시스템은 항상 잘못된 것을 보고하지는 않습니다. 실수를 하면 프로세스의 다른 지점에서 그 사실을 알게 될 수 있습니다.

  • TypeScript 컴파일러는 JavaScript로 컴파일할 때 문제를 보고합니다.
  • 노드를 사용하여 JavaScript를 실행하면 문제가 보고됩니다.
  • Studio Pro에서 앱을 열면 오류/문제가 오류 탭에 보고되거나, 사용자가 만들거나 변경한 내용이 잘못되어 보일 수 있습니다.
  • 정말 심각한 문제라면, Studio Pro가 앱을 여는 동안 예외를 보고하거나 앱을 전혀 열지 못하고 망가질 수 있습니다.

그래서 저는 이렇게 말하게 되었습니다. 항상 대안을 갖고 있어야 한다. 기존 프로젝트라면 프로젝트의 백업 사본을 만드세요. 프로젝트를 복사하고 사본에서 작업하세요. 브랜치에서 작업하세요. 아마도 이 모든 것…

A Mendix SDK Primer - 2부_여러 장의 카드가 놓인 나무 표면 이미지. 한 장의 카드에는 "내가 하는 대로 하지 말고 내가 하는 대로 하라"는 문구가 들어 있음

내가 하는 말대로 하지 말고 내가 하는 대로 하라

좋아요, 많은 숙련된 개발자들이 아마 위의 코드에 움츠러들 것입니다. 이 스크립트가 매우 길고 반복적일 수 있다는 것은 분명합니다(예를 들어, 도메인 모델 변경은 폼과 마이크로플로를 설정하는 것에 비해 간단합니다). 하지만 저는 특히 블로그 게시물의 경우 가능한 한 명확하게 유지하고 싶습니다. 여기에는 의미 있는 변수 이름을 사용하고 복잡한 구문을 피하는 것이 포함됩니다.

우리가 할 수 있는 첫 번째 일은 스크립트를 더 짧게 만드는 데 도움이 되는 함수를 사용하는 것입니다. 예를 들어 다음과 같습니다.

        createEnumerationOption(model, statusEnumeration, "_New", "New");
        createEnumerationOption(model, statusEnumeration, "Hold", "On Hold");
        createEnumerationOption(model, statusEnumeration, "Progress", "In Progress");
        createEnumerationOption(model, statusEnumeration, "Completed", "Completed");
    
    .....
    
    function createEnumerationOption(model: IModel, enumeration: enumerations.Enumeration, name: string, caption_en_US: string)
    {
        const value = enumerations.EnumerationValue.createIn(enumeration);
        value.name = name;
        const caption = texts.Text.createInEnumerationValueUnderCaption(value);
        const translation = texts.Translation.create(model);
        translation.text = caption_en_US;
        translation.languageCode = "en_US";
        caption.translations.push(translation);
    }

여러분이 작성하는 다양한 SDK 스크립트에 포함하고 사용할 수 있는 함수 및/또는 클래스 라이브러리를 구축하는 것이 가치 있다는 것을 알게 될 것입니다.

하지만 긴 스크립트는 다른 이유로도 성능 문제가 발생할 수 있습니다. 메모리 오버헤드를 줄이고 속도를 개선하기 위해 배운 세 가지 사항:

  • FlushChanges를 자주 호출합니다. 이렇게 하면 지금까지 변경한 내용이 로컬 NodeJS 환경에서 다음 환경으로 이동합니다. Mendix ModelServer가 로컬 메모리 사용량을 줄입니다.
  • 가능한 경우 로컬 범위 변수를 사용하세요. 함수를 사용하면 쉽게 할 수 있습니다. 로컬 범위 변수는 정의한 블록에서 생성, 활성화, 소멸되므로 스크립트의 나머지 부분에서 전달되는 데이터 축적이 줄어듭니다. 스크립트의 각 변수 참조는 JavaScript 내부에서 어떤 종류의 조회가 필요하며, 정의한 변수가 많을수록 조회 시간이 더 오래 걸리고(더 많은 메모리가 소모됨)
  • 변수 이름을 짧게, 또는 최소한 더 짧게 유지하세요. 이렇게 하면 실행 시간이 개선되고 긴 스크립트에서 메모리 오버헤드가 줄어듭니다. 분명히 간결함과 명확성 사이에는 상충 관계가 있습니다. 저는 앱을 수정하기 위해 TypeScript 파일을 작성하는 앱이 있는데, 스위치를 켜서 의미 있는 변수 이름(디버깅용) 또는 짧은 변수 이름(프로덕션용)을 생성하도록 설정할 수 있습니다. 아니면 실행하기 전에 생성된 JavaScript를 최소화하기 위해 별도의 도구를 사용할 수도 있지만, 저는 이 방법을 시도해 본 적이 없으므로 얼마나 잘 작동하는지 전혀 모르겠습니다.

아래에 언급된 Github 저장소에서 두 개의 스크립트를 포함했습니다. 하나는 이 블로그에 표시된 간단한 스크립트('createapp.ts')이고 다른 하나는 여기에서 제안한 변경 사항 중 일부를 구현하는 대체 스크립트('createapp2.ts')입니다. 각 스크립트는 동일한 앱을 생성하지만 두 번째 스크립트는 훨씬 짧고 따라가기가 더 쉬울 것 같습니다.

이것으로 이 게시물을 마칩니다. 스크립트는 Github에서 사용할 수 있습니다. https://github.com/Adrian-Preston/SDKBlog2.

세 번째 부분에서는 몇 가지 기능을 개발해 보겠습니다. 곧 공개하겠습니다!

언어를 선택하세요