Вы когда-нибудь хотели сгенерировать код из моделей UML или SysML вашего корпоративного архитектора? Вы пытались настроить структуру шаблона кода корпоративного архитектора? Не отказывайтесь от мечты о конкретных генераторах кода и читайте, как легко они могут быть реализованы.
Необходимость в генераторе кода
Хорошая архитектура программного обеспечения или системы находится на более высоком уровне абстракции по сравнению с реализацией. Это должна быть последовательная модель, которая документирует решения и игнорирует ненужные, часто технические детали. Рассмотрим, например, диаграмму классов на рисунке 1. Она показывает модель домена, которая определяет структуру данных, необходимую для магазина, чтобы позволить клиентам заказывать товары. Свойства каждого класса смоделированы подробно, но другие ненужные аспекты, такие как операции доступа к свойствам, не учитываются.
Если архитектура / дизайн программного обеспечения разрабатывается заранее, прежде чем приступить к реализации, генерация кода может избежать громоздкой и подверженной ошибкам работы. Коммерческие генераторы кода из коробки часто не меняют степень абстракции. Вот почему они часто не соответствуют потребностям проекта.
Шаблон рамка кода Enterprise Architect может быть адаптирована в соответствии с проектом специфических потребностей. Но это требует некоторой начальной подготовки. И часто трудно достичь ожидаемого результата, как описано в разделе Создание кода на основе Eclipse для моделей Enterprise Architect .
Простой проектный генератор кода
Я предпочитаю язык программирования общего назначения, такой как Java или Xtend, для реализации генераторов кода. В частности, Xtend хорошо подходит для реализации шаблонов благодаря своим шаблонным выражениям . Они позволяют встраивать исполняемый код в текст, который будет сгенерирован. Это похоже на программирование на PHP , JSP или JSX . Код в листинге 1 показывает шаблон генерации кода, написанный на Xtend. Он генерирует Java-классы для классов, объявленных на диаграмме классов на рисунке 1.
Джава
1
package com.yakindu.ea.examples.orderingsoftware.template
2
import com.yakindu.bridges.ea.examples.runtime.codegen.EACodegen
4
import org.eclipse.uml2.uml.Class
5
import org.eclipse.uml2.uml.NamedElement
6
class ClassTemplate {
8
"Java") (
9
def String generate(Class element) '''
10
package «element.package.javaQualifiedName»;
11
12
«val superType = element.generals.findFirst[true]?.name»
13
«val extends = '''«IF !superType.isNullOrEmpty» extends «superType»«ENDIF»'''»
14
public«IF element.isAbstract» abstract«ENDIF» class «element.name»«extends» {
15
16
«FOR attribute : element.ownedAttributes SEPARATOR System.lineSeparator»
17
«val type = attribute.type?.javaQualifiedName»
18
«IF 1 != attribute.upper»
19
«val defaultValue = '''new java.util.LinkedList<«type»>()'''»
20
private final java.util.List<«type»> «attribute.name» = «defaultValue»;
21
«ELSE»
22
private «type» «attribute.name»;
23
«ENDIF»
24
«ENDFOR»
25
26
«FOR attribute : element.ownedAttributes SEPARATOR System.lineSeparator»
27
«val type = attribute.type?.javaQualifiedName»
28
«IF 1 != attribute.upper»
29
public List<«type»> get«attribute.name.toFirstUpper»() {
30
return «attribute.name»;
31
}
32
«ELSE»
33
public «type» get«attribute.name.toFirstUpper»() {
34
return «attribute.name»;
35
}
36
«ENDIF»
37
«IF 1 == attribute.upper»
38
39
«val params = '''«type» «attribute.name»'''»
40
public void set«attribute.name.toFirstUpper»(«params») {
41
this.«attribute.name» = «attribute.name»;
42
}
43
«ENDIF»
44
«ENDFOR»
45
}
46
'''
47
48
protected def String getJavaQualifiedName(NamedElement element) {
49
element.qualifiedName.replace("::", ".")
50
}
51
}
52
// Listing 1: Example code generation template written in Xtend
Сгенерированный код Java, показанный в листингах 2, 3 и 4, не выглядит как рукописный, потому что вместо импорта используются квалифицированные имена. Это будет улучшено позже на рисунке 4 методами collectImports
и printImports
.
Джава
xxxxxxxxxx
1
package com.example.orderingsoftware;
2
public abstract class AbstractIDObject {
4
private java.util.UUID id;
6
public java.util.UUID getId() {
8
return id;
9
}
10
11
public void setId(java.util.UUID id) {
12
this.id = id;
13
}
14
}
15
// Listing 2: Java code of class AbstractIDObject generated by the code generation template in Listing 1
Джава
xxxxxxxxxx
1
package com.example.orderingsoftware;
2
public class OrderItem extends AbstractIDObject {
4
private java.math.BigInteger amount;
6
7
private com.example.orderingsoftware.Article article;
8
public java.math.BigInteger getAmount() {
10
return amount;
11
}
12
13
public void setAmount(java.math.BigInteger amount) {
14
this.amount = amount;
15
}
16
17
public com.example.orderingsoftware.Article getArticle() {
18
return article;
19
}
20
21
public void setArticle(com.example.orderingsoftware.Article article) {
22
this.article = article;
23
}
24
}
25
// Listing 3: Java code of class OrderItem generated by the code generation template in Listing 1
Джава
xxxxxxxxxx
1
package com.example.orderingsoftware;
2
public class Order extends AbstractIDObject {
4
private java.util.Date date;
6
7
private com.example.orderingsoftware.Customer customer;
8
9
private final java.util.List<OrderItem> items = new java.util.LinkedList<OrderItem>();
10
public java.util.Date getDate() {
12
return date;
13
}
14
15
public void setDate(java.util.Date date) {
16
this.date = date;
17
}
18
19
public com.example.orderingsoftware.Customer getCustomer() {
20
return customer;
21
}
22
23
public void setCustomer(com.example.orderingsoftware.Customer customer) {
24
this.customer = customer;
25
}
26
27
public java.util.List<com.example.orderingsoftware.OrderItem> getItems() {
28
return items;
29
}
30
}
31
// Listing 4: Java code of class Order generated by the code generation template in Listing 1
Если вы внимательно посмотрите на шаблон в листинге 1, вы поймете, что он ничего не знает о Enterprise Architect. Вместо этого он обрабатывает экземпляры метамодели UML, которая доступна в Eclipse благодаря проекту Eclipse UML 2 . Отсутствующим соединением между Enterprise Architect и UML является EA-Bridge YAKINDU . Это API, который предлагает UML-совместимый доступ для чтения и записи к моделям Enterprise Architect UML и SysML . База данных, лежащая в основе проекта Enterprise Architect, автоматически преобразуется в экземпляры метамодели UML. Это имеет три основных преимущества для вас как разработчика:
- Ваш код совместим с другими инструментами, основанными на проекте UML 2, такими как Papyrus .
- Высокопроизводительный доступ для чтения и записи к моделям Enterprise Architect без необходимости перепроектировать схему базы данных Enterprise Architect.
- Вам не нужно ничего узнавать об API EA-Bridge YAKINDU. Он полностью скрыт для вас как разработчика, потому что EA-Bridge YAKINDU интегрируется в экосистему Eclipse Modeling Framework (EMF).
EA-Bridge YAKINDU поставляется с дополнительной интеграцией Eclipse IDE, которая позволяет реализовывать генераторы кода для конкретного проекта. Эти генераторы кода часто разрабатываются по прототипам и выполняются только в определенном контексте. Таким образом, крайне важно, чтобы усилия по разработке были меньше по сравнению с ручным кодированием. Чтобы реализовать генератор кода для конкретного проекта, все, что вам нужно сделать, это поместить файл EAP в проект Eclipse и аннотировать методы в шаблоне генерации кода с помощью @EACodegen
. Аннотированные методы должны принимать элемент UML, для которого должен быть сгенерирован код, в качестве единственного параметра и возвращать сгенерированный текст. Если ваша модель Enterprise Architect размещена в удаленной базе данных, такой как Microsoft SQL Server , вы можете использовать файл ярлыка вместо файла EAP.
Когда проект создается автоматически или вручную с помощью пункта главного меню «Проект, Очистка…», шаблон запускается для всех классов UML, объявленных во всех файлах EAP. Конечно, рассматриваются только файлы EAP, хранящиеся в проекте шаблона. Сгенерированный код сохраняется в файле, указанном квалифицированным именем класса. Расширение файла определяется аргументом @EACodegen
аннотации. Структуру проекта Eclipse можно увидеть на рисунке 2.
Обратите внимание, что EA-мост YAKINDU - это API. Это позволяет обрабатывать модель Enterprise Architect любым способом. Действительно, первоначальный вариант использования представлял собой комплексные генераторы кода, такие как генератор RTE Autosar, основанный на архитектурах UML.
Создание более одного артефакта на элемент модели
Давайте сделаем пример более интересным, реализовав линейку продуктов с двумя различными подходами к постоянству: один, использующий JPA для хранения данных в реляционной базе данных, и другой, использующий HBase в качестве хранилища больших данных.
Я предлагаю реализовать менеджер сохраняемости, который можно использовать для загрузки и сохранения экземпляров. Только продукт на основе JPA должен позволять запускать и завершать транзакции. Кроме того, я хотел бы разместить аннотации, специфичные для JPA, в классах Java. На рисунке 3 показаны методы, предлагаемые диспетчером постоянства.
Следствием этого является то, что реализация всех шести классов немного отличается в обоих продуктах. Код Java в листингах 5, 6, 7 и 8 показывает фрагмент кода, который будет реализован.
Джава
1
package com.example.orderingsoftware;
2
public class Article extends AbstractIDObject {
4
private String name;
6
public String getName() {
8
return name;
9
}
10
11
public void setName(String name) {
12
this.name = name;
13
}
14
}
15
// Listing 5: Java code of class Article with HBase as persistence approach
Джава
xxxxxxxxxx
1
package com.example.orderingsoftware;
2
import javax.persistence.Entity;
4
import javax.persistence.Table;
5
7
name = "ArticleTable") (
8
public class Article extends AbstractIDObject {
9
private String name;
11
public String getName() {
13
return name;
14
}
15
16
public void setName(String name) {
17
this.name = name;
18
}
19
}
20
// Listing 6: Java code of class Article with JPA as persistence approach
Джава
xxxxxxxxxx
1
public class PersistenceManager implements AutoCloseable {
2
private final Table articleTable;
4
public static final byte[] MODEL_FAMILY = "Model".getBytes();
6
public static final TableName ARTICLE_TABLE_NAME = TableName.valueOf("ArticleTable");
8
public Article getArticle(UUID id) {
10
try {
11
if (id != null) {
12
Get get = new Get(id.toString().getBytes());
13
if (articleTable.exists(get)) {
14
Result r = articleTable.get(get);
15
Article result = new Article();
16
result.setId(id);
17
result.setName(Bytes.toString(r.getValue(MODEL_FAMILY, ARTICLE_NAME_QUALIFIER)));
18
return result;
19
}
20
}
21
return null;
22
} catch (Exception e) {
23
throw new RuntimeException(e.getMessage(), e);
24
}
25
}
26
// ...
28
}
29
// Listing 7: Excerpt of the Java code of class PersistenceManager with HBase as persistence approach
Джава
xxxxxxxxxx
1
public class PersistenceManager implements AutoCloseable {
2
private EntityManager entityManager;
4
public Article getArticle(UUID id) {
6
if (id != null) {
7
return entityManager.find(Article.class, id);
8
} else {
9
return null;
10
}
11
}
12
// ...
14
}
15
// Listing 8: Excerpt of the Java code of class PersistenceManager with JPA as persistence approach
Возможные решения для реализации линейки продуктов:
- Использовать наследование. Для этого потребуется определение интерфейса с открытым API для каждого класса и его реализация для JPA и для HBase. Следствием этого будет то, что остальная часть приложения должна быть настроена для работы только на интерфейсах, а не на конкретных классах.
- Копирование, вставка и изменение реализации для обоих продуктов позволит избежать необходимости изменять остальную часть приложения. Поддержание двух вариантов может показаться разумным. Но так ли это в случае увеличения количества вариантов? Вы должны тщательно продумать все за и против копирования и вставки .
- Использовать генератор кода, который генерирует код продукта. Классы, реализующие шаблоны генерации кода, могут основываться на общей реализации, и каждый подкласс может регулировать специфичные для продукта части.
Я предпочитаю последнее решение. Схема на рисунке 4 показывает измененный шаблон класса из листинга 1. Каждый представленный метод генерирует определенный член класса Java. Это позволяет мне переопределить эти методы в шаблонах для конкретного продукта. Например, в листинге 9 видно, что специфичные для JPA аннотации помещаются перед определением класса.
Джава
1
class JPAClassTemplate extends ClassTemplate {
2
3
def IFile path(Class element, IFile ^default) {
4
val path = "com/example/orderingsoftware/" + ^default.name
5
return getTargetFilePath("jpa", path)
6
}
7
8
"java") (
9
override generate(Class element) {
10
return super.generate(element)
11
}
12
13
override printClassDeclaration(Class element) '''
14
«IF element.isAbstract»
15
16
«ELSE»
17
18
name = "«element.name»Table") (
19
«ENDIF»
20
«super.printClassDeclaration(element)»'''
21
// ...
23
}
24
// Listing 9: Excerpt of the code generation template for JPA written in Xtend
Метод path(Class, IFile)
в подклассах шаблона, аннотированный с помощью @EACodegenFile
, используется для определения целевого местоположения, в котором должен быть сохранен сгенерированный код. У него есть два параметра. Первый - это элемент UML, для которого должен быть сгенерирован код. Второе - это местоположение по умолчанию, в котором должен храниться сгенерированный код. Возвращаемое значение аннотированного метода - это скорректированное местоположение, в котором должен быть сохранен сгенерированный код.
Скриншот на рисунке 5 показывает все шаблоны. Стрелки указывают на файлы, которые генерируются каждым из них. В дополнение к производственному коду генерируется также тестовый код.
Заключение
Современные языки программирования общего назначения, такие как Xtend, хорошо подходят для реализации сложных генераторов кода. Входными данными может быть модель UML, возможно, смоделированная в Enterprise Architect. EA-Bridge YAKINDU на лету полностью скрывает реляционную базу данных, лежащую в основе модели Enterprise Architect, в экземпляры метамодели UML. Нет необходимости изучать проприетарный язык генерации кода, предоставляемый Enterprise Architect, или перепроектировать схему базы данных Enterprise Architect.
Интеграция Eclipse IDE с YAKINDU EA-Bridge позволяет в короткие сроки реализовать генераторы кода для конкретного проекта при низких затратах. Таким образом, вы можете сэкономить много громоздкой, подверженной ошибкам и бессмысленной работы по реализации.
Если вы хотите увидеть и запустить полный пример для себя, попробуйте YAKINDU EA-Bridge . Представленный пример является одним из примеров, поставляемых с EA-мостом YAKINDU.