kryptisch ist relativ! Bezug auf Konzepte der Domäne, formal Business-DSL: nicht technisch, für nicht Techiker geeignet
Java schlecht im Abstrahieren von Struktur, Redundanz Code-Generierung definiert Platform-Mapping (Architekt) Referenz aus anderen DSLs, Wartbarkeit
Java schlecht im Abstrahieren von Struktur, Redundanz Code-Generierung definiert Platform-Mapping (Architekt) Referenz aus anderen DSLs, Wartbarkeit
AST: Abstrakter Syntaxbaum
Java schlecht im Darstellen von Bäumen lohnt schon bei wenigen Tests, Test schreiben macht Spaß Interpreter
Java schlecht im Darstellen von Bäumen lohnt schon bei wenigen Tests, Test schreiben macht Spaß Interpreter
Java schlecht im Darstellen von Bäumen lohnt schon bei wenigen Tests, Test schreiben macht Spaß Interpreter
Raise level of abstraction, Avoids redundancy Separation of concerns, Reuse of domain concepts Higher expressiveness, Ubiquitous language
GPLs können alles Spezielle Aufgaben erfordern spezielle Tools Vorsicht vor wiederverwendbaren von DSLs (Beispiel Feinsäge)
GPLs können alles Spezielle Aufgaben erfordern spezielle Tools Vorsicht vor wiederverwendbaren von DSLs (Beispiel Feinsäge)
Die DSL für DSLs, Eclipse-basiert Define textual DSLs easily, Ready-to-use tool-chain, Easy but flexible customizing and tweaking, Durable foundation with Java Eclipse EMF, Strong community
1 Group
Peter Friese, Software Architect at itemis, added this to the group Xtext
Domänenspezifische Sprachen mit Xtext - Presentation Transcript
Domain-specific languages
with
Dr. Jan Köhnlein
itemis AG
Who‘s that guy?
Jan Köhnlein
• Software Architect, Consultant,
Coach at itemis
Jan Köhnlein
• Software Architect, Consultant,
Coach at itemis
• Several years of experience
in the modeling world
Jan Köhnlein
• Software Architect, Consultant,
Coach at itemis
• Several years of experience
in the modeling world
• Committer to several open-
source projects
Jan Köhnlein
itemis short facts
Spezialist für modellbasierte Entwicklungsverfahren
Gründung im Jahr 2003
Niederlassungen in Deutschland, Frankreich,
Schweiz und Kanada
140 Mitarbeiter
Strategisches Mitglied der Eclipse Foundation
Intensive Verzahnung im Bereich der Forschung
Mitglied von ARTEMISIA
Embedded Software Development
Enterprise Application Development
1
What is a
Domain Specific
Language (DSL)?
A domain-specific language (DSL)
is a formal, processable language
targeting at a specific viewpoint or aspect
of a system.
A domain-specific language (DSL)
is a formal, processable language
targeting at a specific viewpoint or aspect
of a system.
Its semantics, flexibility and notation is designed
in order to support working with that viewpoint
as good as possible.
Boring! Don‘t
you have any
examples?
Rd2-c2
Rd2-c2
“ Queen to c7.
Check.”
“ Rd2-c2 ,
rook at d2 moves to c2.”
Using JPA
@Entity
public class Customer implements Serializable {
private Long id;
private String name;
private Address address;
private Collection<Order> orders = new HashSet<Order>();
private Set<PhoneNumber> phones = new HashSet<PhoneNumber>();
public Customer() {}
@Id
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Address getAddress() {
return address;
}
public void setAddress(Address address) {
this.address = address;
}
@OneToMany
public Collection<Order> getOrders() {
return orders;
}
public void setOrders(Collection<Order> orders) {
this.orders = orders;
}
@ManyToMany
public Set<PhoneNumber> getPhones() {
return phones;
}
public void setPhones(Set<PhoneNumber> phones) {
this.phones = phones;
}
}
@Entity
public class Customer implements Serializable {
private Long id;
private String name;
private Address address;
private Collection<Order> orders = new HashSet<Order>();
private Set<PhoneNumber> phones = new HashSet<PhoneNumber>();
public Customer() {}
@Id
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
} entity Customer {
public void setName(String name) {
this.name = name;
String name
} Address address
public Address getAddress() {
return address; Order* orders
}
public void setAddress(Address address) {
PhoneNumber** phones
this.address = address; }
}
@OneToMany
public Collection<Order> getOrders() {
return orders;
}
public void setOrders(Collection<Order> orders) {
this.orders = orders;
}
@ManyToMany
public Set<PhoneNumber> getPhones() {
return phones;
}
public void setPhones(Set<PhoneNumber> phones) {
this.phones = phones;
}
}
Testing
Testing a Parser
Expr
2+3*7/8
Op +
Parser Number 2 Op /
Op * Number 8
Number 3 Number 7
}
protected String getErrorMessage() {
return errorMessage.toString();
}
Graphics
public boolean isSameStructure(EObject left, EObject right) {
counter = 0;
return internalIsSameStructure(left, right);
}
public boolean internalIsSameStructure(EObject left, EObject right) {
++counter;
if (!isSameClass(left.eClass(), right.eClass())) {
errorMessage.append("Classes are not equal: " + left + " != " + ri
return false;
}
List<EObject> leftChildren = getRelevantChildren(left);
List<EObject> rightChildren = getRelevantChildren(right);
if(leftChildren.size() != rightChildren.size()) {
errorMessage.append("Number of children differs " + left + " " + r
return false;
}
for (int i = 0; i < leftChildren.size(); ++i) {
if(!internalIsSameStructure(leftChildren.get(i), rightChildren.get
errorMessage.append("Children differ " + left + " " + right +
return false;
}
}
Text
return true;
}
protected boolean isSameClass(EClass left, EClass right) {
return left.getName().equals(right.getName())
&& left.getEPackage().getNsURI().equals(right.getEPackage().g
}
protected List<EObject> getRelevantChildren(EObject _this) {
List<EObject> relevantChildren = new ArrayList<EObject>(_this.eContents(
for (Iterator<EObject> i = relevantChildren.iterator(); i.hasNext();) {
EObject next = i.next();
if (!isRelevantChild(_this, next)) {
i.remove();
}
}
return relevantChildren;
}
protected boolean isRelevantChild(EObject container, EObject child) {
return true;
}
protected String getErrorMessage() {
return errorMessage.toString();
}
Graphics
public boolean isSameStructure(EObject left, EObject right) {
counter = 0;
return internalIsSameStructure(left, right);
}
• High-level views
public boolean internalIsSameStructure(EObject left, EObject right) {
++counter;
if (!isSameClass(left.eClass(), right.eClass())) {
errorMessage.append("Classes are not equal: " + left + " != " + ri
return false;
}
List<EObject> leftChildren = getRelevantChildren(left);
List<EObject> rightChildren = getRelevantChildren(right);
if(leftChildren.size() != rightChildren.size()) {
errorMessage.append("Number of children differs " + left + " " + r
return false;
}
for (int i = 0; i < leftChildren.size(); ++i) {
if(!internalIsSameStructure(leftChildren.get(i), rightChildren.get
errorMessage.append("Children differ " + left + " " + right +
return false;
}
}
Text
return true;
}
protected boolean isSameClass(EClass left, EClass right) {
return left.getName().equals(right.getName())
&& left.getEPackage().getNsURI().equals(right.getEPackage().g
}
protected List<EObject> getRelevantChildren(EObject _this) {
List<EObject> relevantChildren = new ArrayList<EObject>(_this.eContents(
for (Iterator<EObject> i = relevantChildren.iterator(); i.hasNext();) {
EObject next = i.next();
if (!isRelevantChild(_this, next)) {
i.remove();
}
}
return relevantChildren;
}
protected boolean isRelevantChild(EObject container, EObject child) {
return true;
}
protected String getErrorMessage() {
return errorMessage.toString();
}
Graphics
public boolean isSameStructure(EObject left, EObject right) {
counter = 0;
return internalIsSameStructure(left, right);
}
• High-level views
public boolean internalIsSameStructure(EObject left, EObject right) {
++counter;
if (!isSameClass(left.eClass(), right.eClass())) {
errorMessage.append("Classes are not equal: " + left + " != " + ri
return false;
}
List<EObject> leftChildren = getRelevantChildren(left);
List<EObject> rightChildren = getRelevantChildren(right);
if(leftChildren.size() != rightChildren.size()) {
errorMessage.append("Number of children differs " + left + " " + r
return false;
}
for (int i = 0; i < leftChildren.size(); ++i) {
if(!internalIsSameStructure(leftChildren.get(i), rightChildren.get
errorMessage.append("Children differ " + left + " " + right +
return false;
}
}
Text
return true;
}
protected boolean isSameClass(EClass left, EClass right) {
return left.getName().equals(right.getName())
}
• Detailed views
&& left.getEPackage().getNsURI().equals(right.getEPackage().g
protected List<EObject> getRelevantChildren(EObject _this) {
List<EObject> relevantChildren = new ArrayList<EObject>(_this.eContents(
for (Iterator<EObject> i = relevantChildren.iterator(); i.hasNext();) {
EObject next = i.next();
if (!isRelevantChild(_this, next)) {
i.remove();
}
}
return relevantChildren;
}
protected boolean isRelevantChild(EObject container, EObject child) {
return true;
}
protected String getErrorMessage() {
return errorMessage.toString();
}
Graphics
public boolean isSameStructure(EObject left, EObject right) {
counter = 0;
return internalIsSameStructure(left, right);
}
• High-level views
public boolean internalIsSameStructure(EObject left, EObject right) {
• Suggests non-formalism
++counter;
if (!isSameClass(left.eClass(), right.eClass())) {
errorMessage.append("Classes are not equal: " + left + " != " + ri
return false;
}
List<EObject> leftChildren = getRelevantChildren(left);
List<EObject> rightChildren = getRelevantChildren(right);
if(leftChildren.size() != rightChildren.size()) {
errorMessage.append("Number of children differs " + left + " " + r
return false;
}
for (int i = 0; i < leftChildren.size(); ++i) {
if(!internalIsSameStructure(leftChildren.get(i), rightChildren.get
errorMessage.append("Children differ " + left + " " + right +
return false;
}
}
Text
return true;
}
protected boolean isSameClass(EClass left, EClass right) {
return left.getName().equals(right.getName())
}
• Detailed views
&& left.getEPackage().getNsURI().equals(right.getEPackage().g
protected List<EObject> getRelevantChildren(EObject _this) {
List<EObject> relevantChildren = new ArrayList<EObject>(_this.eContents(
for (Iterator<EObject> i = relevantChildren.iterator(); i.hasNext();) {
EObject next = i.next();
if (!isRelevantChild(_this, next)) {
i.remove();
}
}
return relevantChildren;
}
protected boolean isRelevantChild(EObject container, EObject child) {
return true;
}
protected String getErrorMessage() {
return errorMessage.toString();
}
Graphics
public boolean isSameStructure(EObject left, EObject right) {
counter = 0;
return internalIsSameStructure(left, right);
}
• High-level views
public boolean internalIsSameStructure(EObject left, EObject right) {
• Suggests non-formalism
++counter;
if (!isSameClass(left.eClass(), right.eClass())) {
errorMessage.append("Classes are not equal: " + left + " != " + ri
return false;
}
List<EObject> leftChildren = getRelevantChildren(left);
List<EObject> rightChildren = getRelevantChildren(right);
if(leftChildren.size() != rightChildren.size()) {
errorMessage.append("Number of children differs " + left + " " + r
return false;
}
for (int i = 0; i < leftChildren.size(); ++i) {
if(!internalIsSameStructure(leftChildren.get(i), rightChildren.get
errorMessage.append("Children differ " + left + " " + right +
return false;
}
}
Text
return true;
}
protected boolean isSameClass(EClass left, EClass right) {
return left.getName().equals(right.getName())
}
• Detailed views
&& left.getEPackage().getNsURI().equals(right.getEPackage().g
• Formal
protected List<EObject> getRelevantChildren(EObject _this) {
List<EObject> relevantChildren = new ArrayList<EObject>(_this.eContents(
for (Iterator<EObject> i = relevantChildren.iterator(); i.hasNext();) {
EObject next = i.next();
if (!isRelevantChild(_this, next)) {
i.remove();
}
}
return relevantChildren;
}
protected boolean isRelevantChild(EObject container, EObject child) {
return true;
}
protected String getErrorMessage() {
return errorMessage.toString();
}
Graphics
public boolean isSameStructure(EObject left, EObject right) {
counter = 0;
return internalIsSameStructure(left, right);
}
• High-level views
public boolean internalIsSameStructure(EObject left, EObject right) {
• Suggests non-formalism
++counter;
if (!isSameClass(left.eClass(), right.eClass())) {
errorMessage.append("Classes are not equal: " + left + " != " + ri
• Individual tools
return false;
}
List<EObject> leftChildren = getRelevantChildren(left);
List<EObject> rightChildren = getRelevantChildren(right);
if(leftChildren.size() != rightChildren.size()) {
errorMessage.append("Number of children differs " + left + " " + r
return false;
}
for (int i = 0; i < leftChildren.size(); ++i) {
if(!internalIsSameStructure(leftChildren.get(i), rightChildren.get
errorMessage.append("Children differ " + left + " " + right +
return false;
}
}
Text
return true;
}
protected boolean isSameClass(EClass left, EClass right) {
return left.getName().equals(right.getName())
}
• Detailed views
&& left.getEPackage().getNsURI().equals(right.getEPackage().g
• Formal
protected List<EObject> getRelevantChildren(EObject _this) {
List<EObject> relevantChildren = new ArrayList<EObject>(_this.eContents(
for (Iterator<EObject> i = relevantChildren.iterator(); i.hasNext();) {
EObject next = i.next();
if (!isRelevantChild(_this, next)) {
i.remove();
}
}
return relevantChildren;
}
protected boolean isRelevantChild(EObject container, EObject child) {
return true;
}
protected String getErrorMessage() {
return errorMessage.toString();
}
Graphics
public boolean isSameStructure(EObject left, EObject right) {
counter = 0;
return internalIsSameStructure(left, right);
}
• High-level views
public boolean internalIsSameStructure(EObject left, EObject right) {
• Suggests non-formalism
++counter;
if (!isSameClass(left.eClass(), right.eClass())) {
errorMessage.append("Classes are not equal: " + left + " != " + ri
• Individual tools
return false;
}
List<EObject> leftChildren = getRelevantChildren(left);
List<EObject> rightChildren = getRelevantChildren(right);
if(leftChildren.size() != rightChildren.size()) {
errorMessage.append("Number of children differs " + left + " " + r
return false;
}
for (int i = 0; i < leftChildren.size(); ++i) {
if(!internalIsSameStructure(leftChildren.get(i), rightChildren.get
errorMessage.append("Children differ " + left + " " + right +
return false;
}
}
Text
return true;
}
protected boolean isSameClass(EClass left, EClass right) {
return left.getName().equals(right.getName())
}
• Detailed views
&& left.getEPackage().getNsURI().equals(right.getEPackage().g
• Formal
protected List<EObject> getRelevantChildren(EObject _this) {
List<EObject> relevantChildren = new ArrayList<EObject>(_this.eContents(
• Well established tools
for (Iterator<EObject> i = relevantChildren.iterator(); i.hasNext();) {
EObject next = i.next();
if (!isRelevantChild(_this, next)) {
i.remove();
}
}
return relevantChildren;
}
protected boolean isRelevantChild(EObject container, EObject child) {
return true;
}
protected String getErrorMessage() {
return errorMessage.toString();
}
Graphics
public boolean isSameStructure(EObject left, EObject right) {
counter = 0;
return internalIsSameStructure(left, right);
}
• High-level views
public boolean internalIsSameStructure(EObject left, EObject right) {
•
++counter;
Suggests non-formalism
if (!isSameClass(left.eClass(), right.eClass())) {
errorMessage.append("Classes are not equal: " + left + " != " + ri
• Individual tools
return false;
}
List<EObject> leftChildren = getRelevantChildren(left);
• Hard to evolve
List<EObject> rightChildren = getRelevantChildren(right);
if(leftChildren.size() != rightChildren.size()) {
errorMessage.append("Number of children differs " + left + " " + r
return false;
}
for (int i = 0; i < leftChildren.size(); ++i) {
if(!internalIsSameStructure(leftChildren.get(i), rightChildren.get
errorMessage.append("Children differ " + left + " " + right +
return false;
}
}
Text
return true;
}
protected boolean isSameClass(EClass left, EClass right) {
return left.getName().equals(right.getName())
}
• Detailed views
&& left.getEPackage().getNsURI().equals(right.getEPackage().g
• Formal
protected List<EObject> getRelevantChildren(EObject _this) {
List<EObject> relevantChildren = new ArrayList<EObject>(_this.eContents(
• Well established tools
for (Iterator<EObject> i = relevantChildren.iterator(); i.hasNext();) {
EObject next = i.next();
if (!isRelevantChild(_this, next)) {
i.remove();
}
}
return relevantChildren;
}
protected boolean isRelevantChild(EObject container, EObject child) {
return true;
}
protected String getErrorMessage() {
return errorMessage.toString();
}
Graphics
public boolean isSameStructure(EObject left, EObject right) {
counter = 0;
return internalIsSameStructure(left, right);
}
• High-level views
public boolean internalIsSameStructure(EObject left, EObject right) {
•
++counter;
Suggests non-formalism
if (!isSameClass(left.eClass(), right.eClass())) {
errorMessage.append("Classes are not equal: " + left + " != " + ri
• Individual tools
return false;
}
List<EObject> leftChildren = getRelevantChildren(left);
• Hard to evolve
List<EObject> rightChildren = getRelevantChildren(right);
if(leftChildren.size() != rightChildren.size()) {
errorMessage.append("Number of children differs " + left + " " + r
return false;
}
for (int i = 0; i < leftChildren.size(); ++i) {
if(!internalIsSameStructure(leftChildren.get(i), rightChildren.get
errorMessage.append("Children differ " + left + " " + right +
return false;
}
}
Text
return true;
}
protected boolean isSameClass(EClass left, EClass right) {
return left.getName().equals(right.getName())
}
• Detailed views
&& left.getEPackage().getNsURI().equals(right.getEPackage().g
• Formal
protected List<EObject> getRelevantChildren(EObject _this) {
List<EObject> relevantChildren = new ArrayList<EObject>(_this.eContents(
• Well established tools
for (Iterator<EObject> i = relevantChildren.iterator(); i.hasNext();) {
EObject next = i.next();
if (!isRelevantChild(_this, next)) {
}
} • Easy to evolve
i.remove();
return relevantChildren;
}
protected boolean isRelevantChild(EObject container, EObject child) {
return true;
}
protected String getErrorMessage() {
return errorMessage.toString();
}
Graphics
public boolean isSameStructure(EObject left, EObject right) {
counter = 0;
return internalIsSameStructure(left, right);
}
• High-level views
public boolean internalIsSameStructure(EObject left, EObject right) {
•
++counter;
Suggests non-formalism
if (!isSameClass(left.eClass(), right.eClass())) {
errorMessage.append("Classes are not equal: " + left + " != " + ri
• Individual tools
return false;
}
List<EObject> leftChildren = getRelevantChildren(left);
• Hard to evolve
List<EObject> rightChildren = getRelevantChildren(right);
if(leftChildren.size() != rightChildren.size()) {
errorMessage.append("Number of children differs " + left + " " + r
• Editing with mouse
}
return false;
for (int i = 0; i < leftChildren.size(); ++i) {
if(!internalIsSameStructure(leftChildren.get(i), rightChildren.get
errorMessage.append("Children differ " + left + " " + right +
return false;
}
}
Text
return true;
}
protected boolean isSameClass(EClass left, EClass right) {
return left.getName().equals(right.getName())
}
• Detailed views
&& left.getEPackage().getNsURI().equals(right.getEPackage().g
• Formal
protected List<EObject> getRelevantChildren(EObject _this) {
List<EObject> relevantChildren = new ArrayList<EObject>(_this.eContents(
• Well established tools
for (Iterator<EObject> i = relevantChildren.iterator(); i.hasNext();) {
EObject next = i.next();
if (!isRelevantChild(_this, next)) {
}
} • Easy to evolve
i.remove();
return relevantChildren;
}
protected boolean isRelevantChild(EObject container, EObject child) {
return true;
}
protected String getErrorMessage() {
return errorMessage.toString();
}
Graphics
public boolean isSameStructure(EObject left, EObject right) {
counter = 0;
return internalIsSameStructure(left, right);
}
• High-level views
public boolean internalIsSameStructure(EObject left, EObject right) {
•
++counter;
Suggests non-formalism
if (!isSameClass(left.eClass(), right.eClass())) {
errorMessage.append("Classes are not equal: " + left + " != " + ri
• Individual tools
return false;
}
List<EObject> leftChildren = getRelevantChildren(left);
• Hard to evolve
List<EObject> rightChildren = getRelevantChildren(right);
if(leftChildren.size() != rightChildren.size()) {
errorMessage.append("Number of children differs " + left + " " + r
• Editing with mouse
}
return false;
for (int i = 0; i < leftChildren.size(); ++i) {
if(!internalIsSameStructure(leftChildren.get(i), rightChildren.get
errorMessage.append("Children differ " + left + " " + right +
return false;
}
}
Text
return true;
}
protected boolean isSameClass(EClass left, EClass right) {
return left.getName().equals(right.getName())
}
• Detailed views
&& left.getEPackage().getNsURI().equals(right.getEPackage().g
• Formal
protected List<EObject> getRelevantChildren(EObject _this) {
List<EObject> relevantChildren = new ArrayList<EObject>(_this.eContents(
• Well established tools
for (Iterator<EObject> i = relevantChildren.iterator(); i.hasNext();) {
EObject next = i.next();
if (!isRelevantChild(_this, next)) {
} • Easy to evolve
i.remove();
•
}
} Editing with keyboard
return relevantChildren;
protected boolean isRelevantChild(EObject container, EObject child) {
return true;
How about
customization?
Convention
over
Configuration
http://www.wordle.net/
Configured with
Google Guice
class MyDslRuntimeModule extends DefaultRuntimeModule {
Class<? extends ServiceInterface> bindService() {
return MyServiceImplementation.class;
}
}
Bird’s Eye View
Extensible Language
Generator
• Generates a lot of Java code
• Composed of fragments
• Customizable
• Add your own
fragments!
Does Xtext
integrate with
other
technologies?
Integration with EMF
Integration with EMF
eclipse
Any EMF-based modeling
Code Generator GMF Editor P R O J E C T
Component
Integration with EMF
eclipse
Any EMF-based modeling
Code Generator GMF Editor P R O J E C T
Component
<<abstract>>
XMIResource
Resource
Integration with EMF
eclipse
Any EMF-based modeling
Code Generator GMF Editor P R O J E C T
Component
<<abstract>>
XMIResource
Resource
Integration with EMF
eclipse
Any EMF-based modeling
Code Generator GMF Editor P R O J E C T
Component
<<abstract>> XMI
XMIResource
Resource
Integration with EMF
eclipse
Any EMF-based modeling
Code Generator GMF Editor P R O J E C T
Component
<<abstract>> XMI
XMIResource
Resource
Integration with EMF
eclipse
Any EMF-based modeling
Code Generator GMF Editor P R O J E C T
Component
<<abstract>> XMI
XMIResource
Resource
XtextResource
Integration with EMF
eclipse
Any EMF-based modeling
Code Generator GMF Editor P R O J E C T
Component
<<abstract>> XMI
XMIResource
Resource
XtextResource
Integration with EMF
eclipse
Any EMF-based modeling
Code Generator GMF Editor P R O J E C T
Component
<<abstract>> XMI
XMIResource
Resource
XtextResource Text
Integration with EMF
eclipse
Any EMF-based modeling
Code Generator GMF Editor P R O J E C T
Component
<<abstract>> XMI
XMIResource
Resource
XtextResource Text
Parser Linker Serializer
ValueConverter ScopeProvider Formatter
converging editors (Xtext and GMF)
koehnlein.blogspot.com
converging editors (Xtext and GMF)
koehnlein.blogspot.com
Questions?
find out more at
http://www.xtext.org
Aktuelle Veranstaltungen
LOP und DSLs - Köln, 15:00
25. August 2009: Deutsches Zentrum für Luft- und Raumfahrt - Falko Riemenschneider,
NRWConf. 2009
27-28. August 2009: Wolkenburg 100, 42119 Wuppertal
Domänenspezifische Sprachen - Lars Corneliussen
12.15 Uhr - 12.45 Uhr
Die 10 Gebote der Architektur - Georg Pietrek
15.20 Uhr - 16.20 Uhr
Scrum: Vom Businessneed zum hochwertigen Produktbacklog, Bonn, 18:30
31.August 2009 - Konferenzhotel Bonn - Sebastian Neus / Dr. Martin Wrangel
Tagung: Mensch und Computer 2009, Berlin, 09:00
07. September 2009 - Leichtgewichtigkeit als Prinzip –
Gestaltung der Webanwendung myPIM durch UCD, FDD und Xtext – Torsten Krohn
Praktische Anwendung von EMF Compare, Dortmund 18:30
14. September 2009 - Harenberg City Center - Dr. Lothar Wendehals
Alle aktuellen Veranstaltungen und weiterführende Informationen auf: http://www.itemis.de/veranstaltungen
1 comments
Comments 1 - 1 of 1 previous next Post a comment