# Tagging the latest version of previously tagged files in CVS Some time ago a set of files ware committed to CVS repository and tagged (lets say with TAG1) . These files have changed a few times since then. Today I needed to tag the latest versions of all files that have ever been tagged TAG1 with TAG2. This ```text cvs -Q log -R -S -rTAG1 . ``` gave me the files I was looking for but with **/cvsroot/** in front and \*\*,v \*\* at the end ```text /cvsroot/path/to/file1,v /cvsroot/path/to/file2,v ... ``` so I had to remove it ```text cvs -Q log -R -S -rTAG1 . | sed s#/cvsroot/## | sed s#,v#\# ``` now I could actually retag these files ```text cvs -q tag TAG2 `cvs -Q log -R -S -rTAG1 . | sed s#/cvsroot/## | sed s#,v#\#` ``` It did the job but I think it's quite ugly way of doing such a "simple" operation. There must be a another (simpler | non \*nix specific) way! # Simple Java program to merge Excel survey results A friend of mine recently asked me about merging survey results, which reminded me I had similar problem about an year ago and have written a peace of code to solve it. It's not a framework or user friendly application and it's not well documented. It was written in a couple of hours to solve particular problem, but in case anyone is interested here is so called [SpreadSurvey](https://MilenDyankov.com/assets/2008-12-29-simple_java_program_to_merge_excel_survey_results/SpreadSurvey.zip). The problem I was facing back then, was how to ask about 20 questions to about 50 people and get their answers into single excel file. There is plenty of tools and services out there for managing polls and surveys, but I was not allowed to place company related data outside company's network. :br So I prepared a survey as simple form in a excel file (unfortunately can not show the actual one here, but it was much like this sample one) and sent it to everyone in my target group, kindly asking them to fill it in and send it back. So I got back about 50 files containing filled forms. The question was how to merge them into single file? Since I'm a Java geek and I had been using Apache POI already and those files had all the same structure, the answer was obvious. A simple Java program could iterate over those files extract any useful data and merge it into a single file. This is how an eclipse quickie called SpreadSurvey was born. ![survey.png](https://MilenDyankov.com/assets/2008-12-29-simple_java_program_to_merge_excel_survey_results/survey.png) Besides the files containing the answers it needs a template for the output file.Template is nothing more than another excel file containing some special tags. *:this*{ss=""} extract data from the cell having exactly the same row and column as the one in template file. *:cell(C\:R)*{ss=""} extracts data from cell at column C and row R.The result file is produced by repeating (horizontally or vertically) the template area containing special tags for each result file. For example if we have a simple template and 3 files containing answers to the sample survey as shown in above images, then this: ```shell $ java com.commsen.ss.MergeResults -t template.xls -r result.xls -d X /tmp/results/* ``` will print ```text Found expression at 1:2 Found expression at 1:3 Found expression at 1:4 Found expression at 1:5 Template expresions found in area 1:2 - 1:5 processing file: /tmp/results/survey_result1.xls processing file: /tmp/results/survey_result2.xls processing file: /tmp/results/survey_result3.xls ``` and will generate file`/tmp/result.xls` which looks like this: ![survey-result-1.png](https://MilenDyankov.com/assets/2008-12-29-simple_java_program_to_merge_excel_survey_results/survey-result-1.png) Alternatively the same data can be organized in columns instead of rows. Just need to provide different template ![survey-template-2.png](https://MilenDyankov.com/assets/2008-12-29-simple_java_program_to_merge_excel_survey_results/survey-template-2.png) and tell SpreadSurvey to repeat vertically ```shell $ java com.commsen.ss.MergeResults -t template2.xls -r result2.xls -d **Y** /tmp/results/* Found expression at 1:2 Found expression at 2:2 Found expression at 3:2 Found expression at 4:2 Template expresions found in area 1:2 - 4:2 processing file: /tmp/results/survey_result1.xls processing file: /tmp/results/survey_result2.xls processing file: /tmp/results/survey_result3.xls ``` and file `/tmp/result2.xls` will look like this: ![survey-result-2.png](https://MilenDyankov.com/assets/2008-12-29-simple_java_program_to_merge_excel_survey_results/survey-result-2.png) So as I said earlier there is no rocket science here. It' s just simple tool I wrote some time ago, that turned out to be useful for someone else. It was never meant to be released or further developed so there is no JavaDoc and the code is not documented either. Only this help screen shows how to use it: ```shell $ java com.commsen.ss.MergeResults --help Usage: MergeResults [OPTION...] FILE... Options: -t, --template-file= Specify the name of the template file. -r, --result-file= Specify the name of the result file. -O, --overwrite Add to overwrite result file if exists. -d, --direction= Specify whether data is added in columns or rows (default is X) -S, --skip-broken Add to skip broken files Help options: -?, --help show this help message --usage show brief usage message ``` Feel free to use it and/or modify it as needed. Don't forget to send me feedback if you do so ;) :br The package contains source code as well as unmodified versions of used libraries : [POI](http://poi.apache.org/){rel=""nofollow""}, [commons-io](http://commons.apache.org/io/){rel=""nofollow""} and [te-common](http://te-code.sourceforge.net/){rel=""nofollow""}. *(Please check appropriate sites regarding licensing and terms of usage)*. # I just finished installing Movable Type Welcome to my new blog powered by Movable Type. Finally got it up and running but it took me a few hours. Cutting the long story short, here is a note of what problems I had and how they were solved. Since there was no Movable Type package for Ubuntu 8.04 I downloaded version 4.23 from {rel=""nofollow""} and unpacked it locally. Then I created `/usr/lib/cgi-bin/movabletype/ `folder, copied in there `*.cgi` files from `/path/to/movabletype/` and made symlinks to these folders ```text /usr/lib/cgi-bin/movabletype/default_templates -> /path/to/movabletype/default_templates /usr/lib/cgi-bin/movabletype/lib -> /path/to/movabletype/lib /usr/lib/cgi-bin/movabletype/plugins -> /path/to/movabletype/plugins /usr/lib/cgi-bin/movabletype/tmpl -> /path/to/movabletype/tmpl ``` I already had Apache configured so it was time to start the installation wizard. But after typing `http://milen.commsen.com/cgi-bin/movabletype/mt.cgi` in Firefox I got: ```text Got an error: Base class package "Class::Accessor::Fast" is empty. (Perhaps you need to 'use' the module which defines that package first.) ``` Took me a while to figure out the name of the package containing this Perl module but once it was discovered ```shell apt-get install libclass-accessor-perl ``` solved this issue and made room for t the next one: ```text Got an error: Base class package "Data::ObjectDriver::BaseObject" is empty. (Perhaps you need to 'use' the module which defines that package first.) ``` The same problem, just different module, except this time there was no Ubuntu package for it. So I had to install it from [CPAN](http://www.cpan.org/){rel=""nofollow""}, which was quite easy to do using `cpan` command. ```shell bash$ cpan cpan shell -- CPAN exploration and modules installation (v1.7602) ReadLine support available (try 'install Bundle::CPAN') cpan> ``` I then typed ```shell cpan> install Data::ObjectDriver ``` and after a while got this message: ```text *** Checking for Perl dependencies... [Core Features] - Test::Exception ...missing. - DBI ...loaded. (1.601) - Class::Accessor::Fast ...loaded. (0.31) - Class::Data::Inheritable ...missing. - Class::Trigger ...missing. - List::Util ...loaded. (1.18) ==> Auto-install the 3 mandatory module(s) from CPAN? [y] ``` Since missing modules depend on other missing modules I just let `cpan` handle dependencies and few minutes later problem was solved and now I had: ```text Got an error: mutiple trigger registration in one add_trigger() call is deprecated. ``` Thanks to Google I found the solution in another blog entry called "[Movable Type with Class::Trigger 0.12](http://www.glorat.net/2008/11/movable-type-with-classtrigger-012.html){rel=""nofollow""}" and after changing ```perl MT::Placement->add_trigger( post_save => \&flush_category_cache, post_remove => \&flush_category_cache ); ``` to ```perl MT::Placement->add_trigger( post_save => \&flush_category_cache ); MT::Placement->add_trigger( post_remove => \&flush_category_cache ); ``` in `MT/Entry.pm` I finally got installation wizard running. At some point, after checking for required modules, the wizard complained about missing `Image::Size` and I had had to do ```text apt-get install libimage-size-perl. ``` There was also warning about missing optional modules so I also installed: ```text libmail-sendmail-perl libsoap-lite-perl libxml-atom-perl perlmagick libgd-gd2-noxpm-perl ``` Rest of configuration process went without problems but after completing the wizard I got: ```text Got an error: Can't locate YAML/Tiny.pm in @INC ... ``` Another Perl module missing? Looks like wizard didn't check for this one. So ```shell apt-get install libyaml-tiny-perl ``` added it but then another one showed up: ```text Got an error: Can't locate JSON.pm in @INC ``` This was fixed by ```shell apt-get install libjson-perl ``` and finally there was "Create Your Account" screen. Creating an account and adding a blog went without problems and I was able to move my old posts from Blogger and add this one. # ATG session tracking cookies and subdomains. If an ATG based web application is available under few subdomains (domain.com, [www.domain.com](http://www.domain.com){rel=""nofollow""}, shop.domain.com) keeping track of session cookies across subdomains may be a challenge. Session tracking cookies (like jsessionid) usually do not have domain property set, which means they are sent back to exactly the same host they came from. So if visitors switch to another subdomain while navigating through the application they would most likely end up having a new session. Depending on what information session holds, the number of visitors and how many simultaneous sessions the server can handle, this may or may not be a problem. The best solution is obviously not to let your visitors change the domain while browsing you site (for example by using relative links only). But if your application occupies the whole domain it may be easier and safer to set the domain property for all relevant cookies. Depending on what ATG modules you use, a number of cookies used for session tracking may vary. Here is how this can be done on ATG 2006.3 running on JBoss 4.0.3.Since JBoss uses Tomcat as web container, we will add a valve to server.xml in which we'll replace a original `HttpServeltResponse` with custom wrapper. Actually this is a slightly modified version of the solution described here {rel=""nofollow""}. So here is our valve: ```java public class CookieRewriteValve extends ValveBase { private String domain = null; private String cookieNames = null; private Set cookiesToModify = null; public void postRegister(Boolean registrationDone) { if (registrationDone.booleanValue()) { if (cookieNames != null && cookieNames.trim().length() > 0) { cookiesToModify = new HashSet(Arrays.asList(cookieNames.toUpperCase().split(",\\s*"))); } } } public void invoke(Request request, Response response) throws IOException, ServletException { CookieModifier.createThreadInstance(cookiesToModify, path, domain, secure, maxAge); response = new CookieRewriteResponseWrapper(response); request.setResponse(response); getNext().invoke(request, response); } public String getDomain() { return domain; } public void setDomain(String domain) { this.domain = domain; } public String getCookieNames() { return cookieNames; } public void setCookieNames(String cookieNames) { this.cookieNames = cookieNames; } } ``` It has to be registered in "Engine" section of `/server//deploy/jbossweb-tomcat55.sar/server.xml` like this ```xml ``` At startup engine initializes declared valves and calls `postRegister` method on each. This is where we read, parse and store arguments. Later on the `invoke` method is called by the engine on every incoming request. This is where we create a new thread local instance of `CookieModifier` and wrap the original `Response` in `CookieRewriteResponseWrapper`. This wrapper is constructed by passing a reference to the original `Response`: ```java public class CookieRewriteResponseWrapper extends org.apache.catalina.connector.Response protected Response res; public CookieRewriteResponseWrapper(Response res) { this.res = res; } .... ``` It also overwrites EVERY public method from `org.apache.catalina.connector.Response` like this: ```java public returnType methodName (parameters...) { return res.methodName (parameters...) } ``` except the `addCookie(Cookie cookie)` method which tries to get an instance of `CookieModifier` and use it to modify the cookie before it delegates the request to the original `Response` object: ```java public void addCookie(Cookie cookie) { CookieModifier cookieModifier = CookieModifier.getInstance(); if (cookieModifier != null) { cookieModifier.modify(cookie); } res.addCookie(cookie); } ``` Finally here is how `CookieModifier` looks like: ```java public class CookieModifier { protected String cookieDomain; protected Set cookiesToModify = null; protected boolean modifyAll = false; private static ThreadLocal threadInstance = new ThreadLocal(); private CookieModifier(Set cookiesToModify, String cookieDomain) { this.cookieDomain = cookieDomain; this.cookiesToModify = cookiesToModify; if (cookiesToModify == null || cookiesToModify.isEmpty()) modifyAll = true; } public static void createThreadInstance(Set cookiesToModify, String cookieDomain) { threadInstance.set(new CookieModifier(cookiesToModify, cookieDomain)); } public static CookieModifier getInstance() { return (CookieModifier) threadInstance.get(); } public void modify(Cookie cookie) { if (modifyAll || cookiesToModify.contains(cookie.getName())) if (cookieDomain != null) cookie.setDomain(cookieDomain); } } } ``` Note the static methods used to store a new instance of this class in current thread. It will become clear why this is important later. So all we have to do now is to pack these classes in a jar file, place it in `/server//lib` and add the valve line to `server.xml`. But if you try to run this, you will notice that while it works for "jsessionid" it does not for "ATG\_SESSION\_ID". This is because ATG itself wraps the `Response` object in DynamoHttpServletResponse which apparently does not make use of the original `addCookie` method! Fortunately though, ATG allows user to have custom implementations of `DynamoHttpServletResponse`. To tell ATG we have a custom implementation we need to add the following configuration to `/atg/dynamo/servlet/dafpipeline/DynamoHandler` component: ```text responseClass=cookie.rewrite.example.DynamoHttpServletResponseWrapper ``` Now when ATG needs to create `DynamoHttpServletResponse` it will actually create an instance of `DynamoHttpServletResponseWrapper`. But how can our implementation obtain access to appropriately configured instance of `CookieModifier`? It can do so because our `CookieRewriteValve`, executed earlier in the same thread, placed an instance of `CookieModifier` in thread local variable. Here is how `DynamoHttpServletResponseWrapper` looks like: ```java public class DynamoHttpServletResponseWrapper extends DynamoHttpServletResponse { public void addCookie(Cookie cookie) { CookieModifier cookieModifier = CookieModifier.getInstance(); if (cookieModifier != null) { cookieModifier.modify(cookie); } super.addCookie(cookie); } } ``` That's it. Once we add this class to the jar created earlier and restart JBoss it should work like a charm ;) # Run GWT application in "hosted mode" from maven It seems to get more and more [cloudy](http://www.infoworld.com/d/cloud-computing/nick-carr-many-ways-cloud-computing-will-disrupt-it-798){rel=""nofollow""} in the IT world these days . It's a matter of time before the rain (of applications) starts. When this happen one will need the proper tools, to be able to add his/hers own few drops. So I though it's about time to start experimenting with [Google Web Toolkit](http://code.google.com/webtoolkit/){rel=""nofollow""}. What I like the most about GWT is it's "hosted mode". The fact that Java code changes reflect the GUI right away and one don't have to wait for generate, compile, build, deploy, ... steps to complete is really speeding up the development process. Since 99% of my projects use [Maven](http://maven.apache.org/){rel=""nofollow""} the first thing to look for (after reading GWT tutorials) was a GWT maven plug-in. No surprise here - there is one ({rel=""nofollow""}). The [GWT docs](http://code.google.com/webtoolkit/gettingstarted.html){rel=""nofollow""} and [gwt-maven-plugin docs](http://mojo.codehaus.org/gwt-maven-plugin/1.1-SNAPSHOT/){rel=""nofollow""} gives a lot of information how to create and build GWT applications. Unfortunately the released version of gwt-maven-plugin (1.0 at the time of writing) does not support hosted mode. The solution is to use the snapshot repository ```xml mojo-snapshots http://snapshots.repository.codehaus.org ``` and the 1.1-SNAPSHOT version : ```xml org.codehaus.mojo gwt-maven-plugin 1.1-SNAPSHOT ``` If the application only contains client code it can be then run in "hosted mode" by simply typing `mvn gwt:run`. The thing docs don't mention (again, at the time of writing this) is that "mvn gwt\:run" will not build the server side of the application. Unless you do this yourself GWT-RPC call will not work. So there are three possible ways: 1. let the Eclipse (or whatever IDE you use) build the classes in `${basedir}/src/main/webapp/WEB-INF/classes`:br I personally don't like this approach. It does not solve the problem if you have multiple modules in maven as the dependent libraries will be still missing. Also you have to add some rules to your SCM system to ignore generated classes. 2. do `mvn compile war:inplace gwt:run`:br This will create extracted version of the war in "${basedir}/src/main/webapp". It will solve the problem even for multiple modules setup but you still will have to deal with the SCM ignore rules. 3. do `mvn compile war:exploded gwt:run`:br This will create extracted version of the WAR in a specified directory (by default it is `${project.build.directory}/${project.build.finalName}` as stated in [Maven WAR Plugin docs](http://maven.apache.org/plugins/maven-war-plugin/exploded-mojo.html#webappDirectory){rel=""nofollow""}). In order for this to work the `gwt-maven-plugin` needs to be told where the exploded war is by adding this to it's configuration in pom.xml : ```xml ${project.build.directory}/${project.build.finalName} ``` :brThis IMHO is the best approach. Solves the problem even in case of multiple modules and your SCM managed folders remain clean. # Creating Liferay portlet with liferay-maven-sdk This post will demonstrate how [liferay-maven-sdk](http://github.com/azzazzel/liferay-maven-sdk){rel=""nofollow""} can be employed to build a Liferay portlet using Liferay's Service Builder feature. For this purpose we will create service-builder-portlet which is capable of displaying a list of players and adding a new player to this list. The model, persistence layer and data access services will be generated by `Service Builder`. But first things first. Download and install `liferay-maven-sdk` if you haven't done so already (have a look at [Download and Install](http://wiki.github.com/azzazzel/liferay-maven-sdk/download-and-install){rel=""nofollow""} page for instructions). Once `liferay-maven-sdk` is installed in your local repository, you can create the portlet. So enter the folder (or create one) where you keep your portlets and execute `mvn archetype:generate` you should see a list of available archetypes starting with ```text Choose archetype: 1: local -> liferay-portlet-archetype (Liferay portlet archetype) 2: local -> liferay-theme-archetype (Liferay theme archetype) 3: internal -> appfuse-basic-jsf (AppFuse archetype for creating a web application with Hibernate, Spring and JSF) 4: internal -> appfuse-basic-spring (AppFuse archetype for creating a web application with Hibernate, Spring and Spring MVC) ... Choose a number: (1/2/3/4/...) :1 ``` Type `1` and press enter to choose `liferay-portlet-archetype`. Then provide `groupId`, `artifactID`, `package` and `version`. For example: ```text Define value for groupId: : com.commsen.liferay.examples.portlet.servicebuilder Define value for artifactId: : service-builder-portlet Define value for version: 1.0-SNAPSHOT: : 1.0 Define value for package: com.commsen.liferay.examples.portlet.servicebuilder: : ``` Additionally you may execute `mvn eclipse:eclipse` to setup Eclipse IDE if, that is what you are using! At this time your portlet skeleton is ready and you may compile it and even create WAR, but it of course does nothing special. Let's now add data model and services. Create file `service-builder-portlet/src/main/webapp/WEB-INF/service.xml`: ```xml SB ``` The portlet projects created by `liferay-portlet-archetype` contain maven profile with `lifray-maven-plugin`'s `build-service` goal attached to `generate-sources` phase. To run `ServiceBuilder` you need to activate the profile: ```sh mvn -P build-service package ``` If you read carefully the messages you will realize that service and persistence classes were generated in `service-builder-portlet/src/main/java-service-api/` folder. Also there should be a few implementation files in `service-builder-portlet/src/main/java/`. Now we'll add custom methods. Open the following file `service-builder-portlet/src/main/java/com/commsen/liferay/examples/portlet/servicebuilder/service/impl/PlayerLocalServiceImpl.java` and paste this code: ```java public void addPlayer(String name, boolean active, int score, Date birthday, String desc) throws PortalException, SystemException { long playerId = CounterLocalServiceUtil.increment(); Player player = PlayerUtil.create(playerId); player.setName(name); player.setActive(active); player.setScore(score); player.setBirthday(birthday); player.setDescription(desc); PlayerUtil.update(player, false); } public List getAllPlayers() throws PortalException, SystemException { return PlayerUtil.findAll(); } ``` We added methods to the implementation class. The next time we compile the code we need to activate the `build-service` profile again in order for `Service Builder` to react on the change and regenerate the API and interfaces. If you want to try it now simply execute ```bash mvn -P build-service compile ``` Now we can start using these services in our portlet. Open `service-builder-portlet/src/main/java/com/commsen/liferay/examples/portlet/servicebuilder/JSPPortlet.java` and add this method ```java @ProcessAction(name = Constants.ADD) public void addPlayer(ActionRequest actionRequest, ActionResponse actionResponse) throws PortletException, IOException { String name = ParamUtil.getString(actionRequest, "name"); boolean active = ParamUtil.getBoolean(actionRequest, "active"); int score = ParamUtil.getInteger(actionRequest, "score"); String description = ParamUtil.getString(actionRequest, "description"); int year = ParamUtil.getInteger(actionRequest, "birthday_year"); int month = ParamUtil.getInteger(actionRequest, "birthday_month"); int day = ParamUtil.getInteger(actionRequest, "birthday_day"); Calendar calendar = Calendar.getInstance(); calendar.set(Calendar.YEAR, year); calendar.set(Calendar.MONTH, month); calendar.set(Calendar.DAY_OF_MONTH, day); try { PlayerLocalServiceUtil.addPlayer(name, active, score, calendar.getTime(), description); } catch (Exception e) { throw new PortletException("Failed to add player", e); } } ``` This method handles adding players to database by calling the method we created earlier. To complete the portlet we only need the JSP page which displays the list and render HTML form to add users. The page source code is available [here](http://github.com/azzazzel/liferay-maven-sdk/blob/master/examples/service-builder-portlet/src/main/webapp/view.jsp){rel=""nofollow""}. That's all! We can now create WAR file (`mvn package`) deploy it and add some players to our database! For more details have a look at portlet's source code available in [examples/service-builder-portlet](http://github.com/azzazzel/liferay-maven-sdk/tree/master/examples/service-builder-portlet){rel=""nofollow""} folder of `liferay-maven-sdk`. # Custom global markup portlet What would you do if a customer demands to "integrate" his Liferay based corporate portal with [Google Analytics](http://www.google.com/analytics){rel=""nofollow""}, [Geminus](http://www.gemius.com/){rel=""nofollow""}, [ClickTale](http://www.clicktale.com/){rel=""nofollow""}, [Crazy Egg](http://www.crazyegg.com/){rel=""nofollow""}, and whole bunch of other analytics tools available out there? As you probably know, such services typically provide some piece of javascript (code or file) which needs to be added to every page of monitored web site. Each service also provides unique customer code/key (which is either already part of the javascript provided or needs to be placed in specific place). Regardless of whether using all of them at the same time is a smart thing to do, there are a few technical problems to solve: - How to add custom code to every portal page - How to deal with unique codes/keys through development, testing, staging, production phases - How to minimize the impact of changing/removing custom code in production environment There are few ways to solve the first case **Make the code part of the theme.** This is easy to do but it has some drawbacks. First of all, depending on how theme is applied, the code may end up on every page in every community or only in few rarely visited pages. Also if your portal uses a number of themes than you need to either make a common theme and make the rest extend it, or you need to add it to each theme. This approach may be a serious maintenance challenge. You may solve the problem with unique codes/keys by using portal properties but you'll not be able to easily modify or remove the java script code if you have disabled (and you should) hot deployment on production servers. **Create custom portlet and add it to every page.** Samuel Kong's excellent post {rel=""nofollow""} explains how to add javascript to every page. However be aware that if you blindly follow the example you'll add the code to EVERY page (not every page in given community). Following this approach you'll have to choose between two options - one portlet having all javascript codes - separate portlet per javascript code (or group of codes) The first one is only acceptable if all javascript codes can ALWAYS be placed together and it kind of violates the "design by responsibility" concept. The second approach on the other hand deploys a lot of boilerplate code which in some cases may have impact on performance. Unique codes/keys may be provided in portlet preferences but still it's not very convenient when portlets are automatically added via `layout.static.portlets.all` property. Also, as you have probably guessed, this approach does not solve the problem with modifying/removing javascript code in production environment. **Use custom-global-markup-portlet** custom-global-markup-portlet was written to solve all of the problems described above. The portlet is based on Samuel Kong's example, but it also provides convenient management interface in Liferay's control panel: ![Custom Global Markup Portlet Configuration](https://MilenDyankov.com/assets/2010-04-06-custom_global_markup_portlet/CustomGlobalMarkupConfig.png) As you can see from the above screenshot portal administrator can easly add/modify/delete any markup (javascript, CSS, HTML, ...). Here is how the portal look like after you save the above markups: ![Portal changed via Custom Global Markup Portlet](https://MilenDyankov.com/assets/2010-04-06-custom_global_markup_portlet/CustomGlobalMarkupResult.png) As you may have already noticed there are a few important features. First of all the markup can be divided into multiple entries and each entry can be enabled/disabled and placed on top (in section) or bottom (before ) of the page. All entries are persisted into database which eliminates potential problems with maintaining different portlet preferences in different environments (development, staging, production). Also note that custom-global-markup-portlet is community scoped, which allows adding markup to pages of specific community. You can download the latest version of custom-global-markup-portlet here: {rel=""nofollow""}. It is part of [Commsen Lifery plugins](http://github.com/azzazzel/Liferay-plugins){rel=""nofollow""} which is free and open source project hosted at [GitHub](http://github.com){rel=""nofollow""} and released under [LGPL license](http://www.gnu.org/licenses/lgpl-2.1.html){rel=""nofollow""}. It is developed with [liferay-maven-sdk](http://github.com/azzazzel/liferay-maven-sdk){rel=""nofollow""} and uses [Git SCM](http://git-scm.com/){rel=""nofollow""}. # Writing Liferay portlet to display a file in a way "tail -f" does Don't know about you but I can't imagine debugging enterprise class applications without having "`tail -f /path/to/log.file`" running in dedicated console window. During development and testing phases (assuming work is done "in house") there is usually no problem with this approach as the whole team have access to servers' log files. This is not always the case with staging and production environments though. These days a lot of companies execute strong security policies which sometimes means that application is only accessible via HTTP. In such case, depending on how you SLA looks like, "*log files provided on demand via e-mail or FTP*" may not be an option. Facing this kind of problem in recent Liferay based project, made me think about creating a portlet capable of displaying log files. Something like WWW based version of "tail -f". This is how Tailgate was born (for those of you looking for solution here is [download page](http://github.com/azzazzel/Liferay-plugins/downloads){rel=""nofollow""}). The rest of this post will concentrate on explaining why it was not "*a max 2h of coding*" as I thought in the begging. Tailgate portlet needs to dynamically show new lines as they are written to the file without reloading the whole portal page. Assuming one portlet instance is configured to display only one file, the solution appears to be straightforward and rather simple: ```text while page is displayed { browser sends AJAX request server checks if there are new lines in given file server responds with list of new lines browser adds lines to appropriate DOM element } ``` While this is in general the whole functionality, there are a few things to consider on both front-end (client) and back-end (server) side. ## front-end As far as front-end is concern there are 2 main things to consider: ### **Multiple instances on the same page** This is a bit tricky and requires good understanding of how portals work. When a portlet is developed it contains common code for all instances. So if multiple instance can be placed on the same portal page then the common code should be able to distinguish instances. This is even more important with AJAX requests. In this case we have a common logic for sending and receiving AJAX requests and updating DOM model. However when portlet instance sends AJAX requests it needs to provide instance specific set of parameters as part of the URL. Also when response is received only DOM elements belonging to this particular instance need to be updated. The common JavaScript code is provided in `tailgate.js` file. The portlet uses also [jQuery timers module](http://plugins.jquery.com/project/timers){rel=""nofollow""} (`jquery.timers.js`) which provides high level abstraction of setTimeout and setInterval. These files are part of the portlet code. Including them in HTML page is done by adding ```text /js/jquery.timers.js /js/tailgate.js ``` to `WEB-INF/liferay-portlet.xml`. This way Liferay will add appropriate links in HEAD section of HTML page and will do this only once regardless of how many portlet instances page contains. The `tailgate.js` defines the Tailgate object and `tailgateInstances` map for holding information about Tailgate instances: ```text var tailgateInstances = new Array(); function Tailgate(lines, url) { this.lines=lines this.url=url } ``` When portlet instance is rendered it takes care to prefix DOM element ids with it's namespace ```text ...
    ``` , add itself to `tailgateInstances` map ```html ``` Note that all functions in `tailgate.js` are designed to accept namespace as parameter. This way when called they can either get appropriate Tailgate instance from `tailgateInstances` map and then check for given property (for example url), or find and update DOM elements prefixed with this namespace. ### **Control the CPU and memory usage** Monitored file can change very fast (application could add few megabytes is just a second) therefore at first it seems to be very important to query back-end as often as possible. This however results in very high CPU usage. In fact going down below 10ms may force you to kill your browser in order to recover your system. Experimenting with different values I finally decided that refresh rate of one second is a reasonable compromise. Another thing is memory. If lines are only added but never deleted then after a while the browser will be trying to display a tens of megabytes of HTML code. Therefore Tailgate is configured to only display last X lines. It renders each line as `
  • ` element and when new line is added, a new `
  • ` element is added to the parent `
      `. Then it checks the size of `
        ` and if it's bigger than X, old lines are deleted from DOM model. Luckily [jQuery](http://jquery.com/){rel=""nofollow""} comes with convenient methods so this is really easy to implement: ```js jQuery('#' + namespace + 'list').append(data) var maxLines = tailgateInstances[namespace].lines var lines = jQuery('#' + namespace + 'list li').length if (lines > maxLines) { jQuery('#' + namespace + 'list li') .slice(0, lines - maxLines) .remove() } ``` ## back-end The line "*server checks if there are new lines in given file*" in the above pseudo algorithm is also way oversimplified. The [RandomAccessFile](http://java.sun.com/j2se/1.5.0/docs/api/java/io/RandomAccessFile.html){rel=""nofollow""} class provides the functionality to position at specific place in file and start reading. However creating a new instance on every request is not a very smart thing to do. Even if it is somehow cached per portlet instance, there still could be many instances monitoring the same file *(in the future each may provide different filtering, highlighting, etc.)* Also, depending on what file system is used *(for example old versions of NFS)*, there may be some locking issues when multiple threads try to access the given file at the same time. Therefore it looks like the optimal solution would be to have one thread continuously reading the file and updating in memory buffers provided by specific instances. Here is the activity diagram: ![Tailgate activity diagram](https://MilenDyankov.com/assets/2010-05-04-writing_liferay_portlet_to_display_a_file_in_a_way_tail_-f_does/TailgateActivityDiagram.png) This looks simple enough but again there are a couple of things to think about: ### **synchronization** Since there are two thread operating on the same buffer (one writing and one reading) the buffer's read and write operations need to be synchronized. Otherwise a typical [Memory Consistency Errors](http://java.sun.com/docs/books/tutorial/essential/concurrency/memconsist.html){rel=""nofollow""} may occur. ### **buffer size** Buffers need to have fixed size in order to avoid memory leaks! Since the front-end also displays a limited amount of data, the same configuration parameter can be used to define both the number of lines displayed and buffered. Then every time a new line is added to the the oldest one is removed if buffer the size is reached: ```text public boolean addLine(final String line) { boolean result; synchronized (buffer) { result = buffer.add(line); if (buffer.size() > maxSize) { buffer.remove(); } } return result; } ``` ### **when to stop reading** If you have carefully examined the activity diagram above you may have noticed that buffers are never unregistered. This is not an error, it is simply impossible to tell when a buffer is no longer needed. One may argue that user could send us appropriate message by clicking on something saying "*I'm done watching, please stop the buffer!*". My experience shows such approach is often misunderstood and misused *(by that I mean used too often or not at all)*. So how can one prevent `FileMonitor` thread from running forever\*(it will run as long as there are buffers)\*? Let the garbage collector do it's job! Buffers are stored in portlet sessions, so instead of relaying on user interaction, Tailgate relays on buffers being garbage collected once portlet session is closed. As you probably know an object becomes eligible for garbage collection when there are no hard references to it. Is this the case with buffers? As the diagram shows, buffers are referenced in 2 other places (marked in red) - `FileMonitoringEngine` needs to keep track of which buffer is assigned to which monitor and `FileMonitor` needs to maintain a list of buffers to write to. But here is the difference, in order to leverage the garbage collector's ability to determine buffers' reachability, both `FileMonitoringEngine` and `FileMonitor` use weak references. `FileMonitoringEngine` uses in fact standard WeakHashMap to store the mapping. `FileMonitor` only needs a Set of weak references but there is no WeakHashSet class. In JDK 6 there is a convenient "`newSetFromMap(Map map)`" method available in `java.util.Collections` class. In order to be compatible with JDK 5 the behavior of this the JDK 6 method had to be implemented as part of Tailgate portlet. This way as soon as portlet session gets garbage collected there are no hard references to the buffer and it is collected as well. When all buffers are garbage collected the `FileMonitor` thread ends. The first request instantiating new buffer will start new `FileMonitor` thread which will again run as long as there is someone interested in receiving results. ## Conclusion I'm well aware I'm not writing anything really revealing here. But after spending some hours on Tailgate portlet I thought I would write about the problems and solutions. Hopefully you at least learned a little something from this experience. In case you are interested, [Tailgate's source code is available at GitHub.](http://github.com/azzazzel/Liferay-plugins/tree/master/tailgate/){rel=""nofollow""} # Liferay Portal 6 Enterprise Intranets review ![](https://www.packtpub.com/sites/default/files/imagecache/productview/0387_Liferay%20Portal%206%20Enterprise%20Intranets.jpg){align="right"} A few weeks ago I was asked by [Packt publishing](http://www.packtpub.com){rel=""nofollow""} to review the new [Liferay Portal 6 Enterprise Intranets](https://www.packtpub.com/liferay-portal-6-enterprise-intranets/book){rel=""nofollow""} book. Going through over 650 pages took me some time but finally I'm ready to share my thought about it. By now you are probably scanning the text for something like "In general this is \_**\_ book". Don't bother, I'm not going to generalize in this post. In fact, what you put in place of \_\_** depends on who you are, what is your Liferay background, and what you expect to learn. I'm a software developer and I have been using Liferay for a few years now. When I heard about the book, my imagination draw a visions of me learning "how Liferay internal mechanisms work" or "what some of those strange configuration parameters are used for" or "how to tweak portlets' functionality and performance" or ... Of course, none of this had happened! This does not mean the book is bad, it only means I had too big expectations. If you are like me, then you may even get bored trying to read it form side to side. **One thing that I really miss in this book is pointing out what's new in Liferay 6**. I know well 5.2.x version and I would appreciate a special style or sign indicating new *(or updated)* feature in Liferay 6. This way more advanced readers could concentrate on things that are potentially of their interest and skip the part they are already familiar with. If it wasn't for the review I would probably scan through the first few chapters for things I don't know *(and yes there are some interesting features to learn even if you already know Liferay)* and then pay more attention at the last ones. The good news is the **chapter 11 called "[Ongoing Admin Tasks](https://docs.google.com/viewer?url=https%3A%2F%2Fwww.packtpub.com%2Fsites%2Fdefault%2Ffiles%2F0387-Chapter-11-Ongoing-Admin-Tasks.pdf){rel=""nofollow""}"** is freely available online so you can have a look and see how it compares to the level of your knowledge. But since *"This book is for system administrators or experienced users (not necessarily programmers) who want to install and use Liferay in their teams or businesses without dealing with complex code. Prior knowledge of Liferay is not expected for this book."* I decided to pretend I know almost nothing about Liferay and simply followed the instructions in the book *(I wish I had the PDF version, it would save me some typing)*. When I got rid of "come on, I already know all this" attitude, I had to admit the book is quite well organized. **Each chapter typically starts with explaining the basic functionality then goes through some configuration options and ends up instructing the reader how to define appropriate permissions.** Sure, after the first two chapters you already know the pattern for defining permissions and there is no need to repeat it over and over again. But on the other hand if you intend to later one use the book as a quick reference or cheat-sheet, it may come handy. **The book will explain almost** *(as with every book the thing you are particularly interested in, will be probably missing)* **every intranet feature you may need.** Moreover it sometimes goes a bit off topic as I can hardly imagine configuring WAP or SEO for intranet site. It will not give too much details though, only enough to get you started and sometimes make small customizations. For example, it will not tell you how exactly portlet properties affect Liferay, but will give you a list of all properties related to current component with short explanation. **Being frequent reader of Liferay forums I'm convinced there is need for such book.** The number of questions starting with "How can I ..." speaks for itself and from what I've noticed people are already referring to this book for answers. **But even if you are more advanced Liferay user, you may still find in the book valuable information about open search, clustering, reporting and audit logging, integration with Alfresco, CMIS and many others.** Just remember, don't expect too much details. # More "Simple" than "XStream" I guess every Java developer dealing with JAVA/XML serialization/deserialization knows about [XStream](http://xstream.codehaus.org){rel=""nofollow""}. I was using it for years until yesterday. What happened yesterday? I found out XStream dos not work out of the box with [GAE](http://code.google.com/appengine/){rel=""nofollow""}. Well is's not exactly XStream's fault. A lot of stuff does not work properly with GAE due to its limitations and odd security restrictions. But my hope to quickly find patch/workaround, went away as soon as I realized the problem was reported to XStream over an year ago ({rel=""nofollow""}) and there is still no good solution. This way I was forced to look for alternatives. And I found [Simple](http://simple.sourceforge.net/){rel=""nofollow""}! Conceptually it's a very similar to XStream. Serialization is really simple to use and revolves around several annotations and a single persister object. I got the impression it's noticeably faster than XStream. It's feature list is quite long (it even claims to be bean version tolerant) but so far I've used the standard stuff like converters, transformers, persister, etc. However since "Simple" - does not depend on 3rd party libraries - is available in central Maven repository - works out of the box with GAE - is capable of doing everything XStream is doing it's about to become my number one XML serialization/deserialization tool. At least until I discover it's dark sides. # Just added 'J' in front of WebThumb Yep, good guess, a Java API to [bluga.net webthumb](http://webthumb.bluga.net){rel=""nofollow""} in now available. Making your Java application display website thumbnails is now something really easy to implement. Get your API KEY form [bluga.net webthumb](http://webthumb.bluga.net){rel=""nofollow""}, download [JWebThumb](http://sourceforge.net/projects/jwebthumb/files/){rel=""nofollow""} and start requesting and fetching thumbnails with just a few lines of code. Why [bluga.net webthumb](http://webthumb.bluga.net){rel=""nofollow""}? Nope, I'm not gonna tell you it's the best tool out there, having unique features, ... or any of this marketing bla bla. The truth is, it was the first tool I found, that met my requirements - custom size thumbnails - support for both JPG an PNG - web services or REST based API - either creates thumbnail instantly or sends notification when done - free version I gave it a try and it turned out it's quite fast and reliable. Not that I have been heavily using it, but so far I haven't had any problems with it. Most of the time when I request a thumbnail it estimates it will take about 20 second but in fact my servlet receives notification in less than 2 seconds. So, after a few days of playing with webthumb's API I had a pile of Java snippets testing different aspects of it. Organizing the chaos resulted in version 0.1 of **JWebThumb** project. Added data model and error handling on the top of that and 0.2 version was ready to go public (under LGPL). As usual Maven helped create a [project site](http://jwebthumb.sourceforge.net/){rel=""nofollow""} from where you can learn how to use **JWebThumb**, the [source code](http://github.com/azzazzel/JWebThumb){rel=""nofollow""} is on GitHub and [downloads](http://sourceforge.net/projects/jwebthumb/files/){rel=""nofollow""} are available on SourceForge. If you find a bug or missing feature don't hesitate to [create an issue](http://github.com/azzazzel/JWebThumb/issues){rel=""nofollow""}. I consider 0.2 an early beta version. It works but it's not extensively tested and may have bugs. It uses [XStream](http://xstream.codehaus.org/){rel=""nofollow""} for XML serialization and deserialization thus you can not yet use it on [GAE](http://appengine.google.com/){rel=""nofollow""}. So version 0.3 is already underway having [XStream](http://xstream.codehaus.org/){rel=""nofollow""} replaced by [Simple](http://simple.sourceforge.net/){rel=""nofollow""} so it works on [GAE](http://appengine.google.com/){rel=""nofollow""}. But before I release it I would like to test it a bit more and perhaps add support for ['status' requests](http://webthumb.bluga.net/apidoc#status){rel=""nofollow""} missing in 0.2. So stay tuned, it shouldn't take too long. # Liferay plug-ins adapted to work with Liferay 6.0.5 As soon as Liferay 6.0.5 was released I decided to adapt my plug-ins to the newest framework version. But as we all know, being determined to do something is not the same as having the time to do it. The good news is, a few days ago I finally quit saying myself "*never mind, you'll do it tomorrow*" and started getting things done. And now I'm happy to announce that [Custom Global Markup](http://www.liferay.com/downloads/liferay-portal/community-plugins/-/software_catalog/products/4846919){rel=""nofollow""}, [Tailgate](http://www.liferay.com/downloads/liferay-portal/community-plugins/-/software_catalog/products/4924578){rel=""nofollow""} and [Liferay-UI Taglib Demo](http://www.liferay.com/downloads/liferay-portal/community-plugins/-/software_catalog/products/4906953){rel=""nofollow""} are already upgraded to work with Liferay 6.0.5. Please read my previous posts "[Custom global markup portlet](https://MilenDyankov.com/blog/2010/04/custom_global_markup_portlet)" and "[Writing Liferay portlet to display a file in a way "tail -f" does](https://MilenDyankov.com/blog/2010/05/writing_liferay_portlet_to_display_a_file_in_a_way_tail_-f_does)" for more information about [Custom Global Markup](http://www.liferay.com/downloads/liferay-portal/community-plugins/-/software_catalog/products/4846919){rel=""nofollow""} and [Tailgate](http://www.liferay.com/downloads/liferay-portal/community-plugins/-/software_catalog/products/4924578){rel=""nofollow""} respectively. ### The plugins for 5.2.3 versions ware build by [liferay-maven-sdk](https://github.com/azzazzel/liferay-maven-sdk){rel=""nofollow""} and this was one of my biggest concerns. I was a bit warred about how difficult will be to move to the native Maven support in Liferay 6. However, not trusting my own experience and struggling to think logically, I realized it should be only a matter of modifying the POM. And indeed it was. I simply removed 5.2.3 dependencies and maven plugins and added 6.0.5 ones ([here is the diff](https://github.com/azzazzel/Liferay-plugins/commit/6476d09685ba47559d860aa30eb3bb48caa42df4#diff-0){rel=""nofollow""}) and I was able to build my plugins using Liferay's 6 maven artifacts. Even ServiceBuilder worked without problems and additional configurations. Apart from adapting the code there is one more significant change. While version control and issue tracker remain on GitHub, binary files were moved to new SourceForge project called "[liferay-plugins](http://sourceforge.net/projects/liferay-plugins/){rel=""nofollow""}". You are welcome to visit it and download, comment, rate, review, .... There are also some [screenshots](http://sourceforge.net/project/screenshots.php?group_id=368520){rel=""nofollow""} of the plug-ins. While I thought having screenshots is cool, a (much younger) collegue of mine, pointed out that these days every self respecting project has instructional videos on YouTube. Well, my generation didn't spend their whole childhood in front of the TV so please excuse my ignorance. Not to give anyone another reason to complain here are the videos: # Liferay User Interface Development ![](https://www.packtpub.com/sites/default/files/imagecache/productview/2626OS_Mockup%20cover_0.jpg){align="right"} The last 6 months were extremely busy for me. A lot had happened in both private and professional aspects. Having that in mind I'm quite happy I manged to deal with completely new experience - being a technical reviewer of "[Liferay User Interface Development](http://www.packtpub.com/liferay-user-interface-development/book){rel=""nofollow""}" - a new book published recently by [Packt Publishing](http://www.packtpub.com){rel=""nofollow""}. Despite the lack of spare time I was somehow able to read and comment on drafts of 10 chapters covering things like theme development, layout templates, [velocity templates](http://velocity.apache.org/){rel=""nofollow""}, tag libraries, [AlloyUI](http://alloy.liferay.com/){rel=""nofollow""}, and much more. Hopefully my comments and opinions ware useful for the authors and help them improve the book. I still don't have the published book *(I'm about to receive my copy soon)* but based on what I have read in the drafts I can say it's worth recommendation. The book puts together most of the peaces related to UI development in [Liferay](http://www.liferay.com){rel=""nofollow""}. Of course it does not cover all the details *(no book can do this)* but it often goes beyond the basics. In fact one of the chapters I found very informative and well organized is freely available on-line ({rel=""nofollow""}) so you can check for yourself. There is also quite good chapter about [AlloyUI](http://alloy.liferay.com){rel=""nofollow""} - especially useful if you have to move form **Liferay 5.2** to **Liferay 6.0** and replace [JQuery](http://jquery.com/){rel=""nofollow""} with [AlloyUI](http://alloy.liferay.com){rel=""nofollow""}. The migration and upgrade process is also one of the subjects in the final chapter "*User Interface in Production*" which among others covers things like workflows, social UI and friendly URLs. Of course, there are too many options when it comes to UI development ([JSF](http://www.oracle.com/technetwork/java/javaee/javaserverfaces-139869.html){rel=""nofollow""}, [Struts](http://struts.apache.org/){rel=""nofollow""} & [Tiles](http://struts.apache.org/1.x/struts-tiles/index.html){rel=""nofollow""}, [Vaadin](http://vaadin.com/home){rel=""nofollow""}, [JQuery](http://jquery.com/){rel=""nofollow""}, [AlloyUI](http://alloy.liferay.com){rel=""nofollow""}, Liferay UI taglib, [Velocity](http://velocity.apache.org/){rel=""nofollow""}, [Freemarker](http://freemarker.sourceforge.net/){rel=""nofollow""} to mention just a few) and it's not possible to describe them all in details. This book concentrates on Liferay's approach to UI development which *(in version 6)* is a powerful mix of [Velocity templates](http://velocity.apache.org/){rel=""nofollow""}, tag libraries and [AlloyUI](http://alloy.liferay.com){rel=""nofollow""}. And yes, if you use [Vaadin](http://vaadin.com/home){rel=""nofollow""} or [JSF](http://www.oracle.com/technetwork/java/javaee/javaserverfaces-139869.html){rel=""nofollow""} or [Freemarker](http://freemarker.sourceforge.net/){rel=""nofollow""} for your day to day work, it may not be a book for you as all you'll find in it about this technologies is references to online recourses. But going back back to "standard" UI approach, the authors really try to teach the reader by providing many examples, mini tutorials and references to portal's and portlet's source code. One thing I was pointing out while reviewing the book was the lack of good reference documentation in two areas: tag libraries and [Velocity](http://velocity.apache.org/){rel=""nofollow""} variables and macros . One may argue whether such a book is the best place to provide such documentation. Maybe not, I'm not sure. On the other hand comparing the number of questions posted on Liferay's forums about what tags are available and what their parameters mean with briefness of documentation available at {rel=""nofollow""}, makes me thing that more in depth information on the subject would make this book more attractive for wider group of Liferay users. Same thing about [Velocity](http://velocity.apache.org/){rel=""nofollow""} macros and variables. Event the most advanced reference about velocity variables in Liferay I know about - {rel=""nofollow""} - seems to be incomplete and sometimes inaccurate. I don't know if my suggestions were taken into account. Honestly, I doubt the authors had enough time and room to be able to dig deeply into those subjects. But hopefully at least those tags, variables and macros used in the examples and better described. But please don't get me wrong. The fact that I will always point out something that in my opinion can be done better does not mean the book is bad. Assuming one don't need to get deeply into Liferay's internals and do not worry too much about what exactly any particular line in the sample code does, this book provides complete information about Liferay UI development. I personally learned a lot of new things although I've been using Liferay for a few years. The bottom line is: **whether you have been mastering Liferay portal for a while or just entering the world of Liferay, this book will help you understand how to develop complex and user friendly user interfaces in Liferay Portal 6.0** # Liferay GWT portlet - how to make it "instanceable" and use GWT RPC Every once in a while somebody asks about writing [Liferay](http://liferay.com){rel=""nofollow""} portlets in [GWT](http://code.google.com/webtoolkit/){rel=""nofollow""}. It seems a lot of people are successfully using [GWT](http://code.google.com/webtoolkit/){rel=""nofollow""} with [Liferay](http://liferay.com){rel=""nofollow""} but surprisingly I couldn't find any complete tutorial on the subject. There are a of course tutorials explaining the basics but what they concentrate on, is how to build *single-instance* and *client-side-only* portlets. This is good enough to get you started but chances are sooner or later you'll need to place two instances of the same [GWT](http://code.google.com/webtoolkit/){rel=""nofollow""} portlet on the same page and/or implement [GWT RPC](http://code.google.com/intl/pl/webtoolkit/doc/latest/tutorial/RPC.html){rel=""nofollow""} to make use of the [Liferay](http://liferay.com){rel=""nofollow""} services. I've reached that point myself sometime ago and unfortunately had to solve the problem myself. Then I wrote sample portlet called [gwt-chatrooms-portlet](https://github.com/azzazzel/gwt-chatrooms-portlet){rel=""nofollow""} to demonstrate the solution and hopefully save you some time. So here is a step by step tutorial how to create GWT portlet for [Liferay 6.0.5](http://www.liferay.com/downloads/liferay-portal/available-releases){rel=""nofollow""} which: - allows many instances to be placed on the same page - uses [GWT RPC](http://code.google.com/intl/pl/webtoolkit/doc/latest/tutorial/RPC.html){rel=""nofollow""} for client-server communication The gwt-chatrooms-portlet is very simple portlet allowing users to enter chat room *(by typing in it's name)* and then chat with other users. Here is screenshot : ![screenshot of chatrooms GWT portlet](https://MilenDyankov.com/assets/2011-01-29-liferay_gwt_portlet_how_to_make_it_instanceable_and_use_gwt_rpc/chatrooms-gwt-portlet.png) As you can see, multiple instances can be added on the same page allowing users to chat in more than one room at a time. Message persistence API is generated using Liferay's [ServiceBuilder](http://www.liferay.com/documentation/liferay-portal/6.0/development/-/ai/service-builder){rel=""nofollow""}. The [GWT](http://code.google.com/webtoolkit/){rel=""nofollow""} client code uses [GWT RPC](http://code.google.com/intl/pl/webtoolkit/doc/latest/tutorial/RPC.html){rel=""nofollow""} to receive room messages from the server and store new ones. Here is how to build it. ## Generate the portlet in Liferay SDK OK first things first. Make sure you have [Liferay 6.0.5](http://www.liferay.com/downloads/liferay-portal/available-releases){rel=""nofollow""} and [Liferay plugins SDK-6.0.5](http://www.liferay.com/downloads/liferay-portal/additional-files){rel=""nofollow""} installed and configured. Then create new portlet by tying : ```text create.sh gwt-chatrooms "Chatrooms - Sample GWT portlet" ``` in **\/portlets**. This will create **gwt-chatrooms-portlet** folder and the standard set of files. [This is in my first commit](https://github.com/azzazzel/gwt-chatrooms-portlet/commit/7c65913dca9d18df440831c39fb76b897570834a){rel=""nofollow""}. ## Add GWT compile task to the build Before you continue with actual development, you have to add an ant task to **\/portlets/gwt-chatrooms-portlet/build.xml** responsible for compiling GWT code. The code is available [here](https://github.com/azzazzel/gwt-chatrooms-portlet/blob/57c4c010c7aad160bb84e4ecbae02df94119c29e/build.xml){rel=""nofollow""}. You have probably noticed my task uses **${gwt.sdk}** variable. To use it as is you have to set it's value in **\/build.\.properties** file : ```text gwt.sdk= ``` ## Create GWT module and entry point class Now you are ready for GWT portlet development. Create GWT module file **gwt-chatrooms-portlet/docroot/WEB-INF/src/com/commsen/sample/portlet/chatrooms/Chatrooms.gwt.xml** ```xml ``` and GWT entry point class **\/portlets/gwt-chatrooms-portlet/docroot/WEB-INF/src/com/commsen/sample/portlet/chatrooms/client/GWTEntryPoint.java** ```java public class GWTEntryPoint implements EntryPoint { @Override public void onModuleLoad() { RootPanel.get("chatrooms-portlet").add(new HTML("This is the GWT chat rooms portlet.")); } } ``` In **view\.jsp** remove the default message and add **div** element with id "chatrooms-portlet" - this is the container for GWT code. Next replace default value of "footer-portlet-javascript" in **\/portlets/gwt-chatrooms-portletdocroot/WEB-INF/liferay-portlet.xml** with "/Chatrooms/Chatrooms.nocache.js" *(assuming "Chatrooms" is the name of your GWT module)*. [Here are my changes](https://github.com/azzazzel/gwt-chatrooms-portlet/commit/9623586fc32433a11bdadf6298d40e66b680c661){rel=""nofollow""}. Before you deploy and see your portlet in action you need to also add ```text false ``` into portlet's configuration in **liferay-portlet.xml** file to make sure Liferay will not use AJAX to load portlet's content . However if you try to place 2 instance of the portlet on the same page you'll be surprised that one of them (or even both in some browsers) shows up empty. This is because the HTML page now has more than one **div** element with the same id "chatrooms-portlet". ## Make GWT code recognize portlet instances To overcome the problem you can make use of portlet's instance id and add it to the id of the **div** element in every portlet. To prepare for this change, create a javascript array (say **chatroomPortletInstances**) to hold portlet instance ids. [Here is how I did it](https://github.com/azzazzel/gwt-chatrooms-portlet/commit/6cd49209efb204be0876fa4713daeb5e3b9575c7){rel=""nofollow""}. Then modify **view\.jsp** so that it adds the portlet id into **chatroomPortletInstances** and make the **div** id contain the portlet id. See [my code](https://github.com/azzazzel/gwt-chatrooms-portlet/commit/bf7e759dbe5c6ca6f83ee2ad437af56fef5def21){rel=""nofollow""} for example. Now you need to let the GWT code "know" about portlet instances. So instead of doing all initialization in the entry point class *(which I consider bad practice anyway)* create a dedicated class (say **Chatroom**) with constructor accepting portlet id as parameter. This way you can create dedicated **Chatroom** instance for every portlet instance. In order to do that you need to know the ids of all portlet instances available on the page and this is where **chatroomPortletInstances** array comes in. Thanks to GWT's [JSNI](http://code.google.com/intl/pl/webtoolkit/doc/latest/DevGuideCodingBasicsJSNI.html){rel=""nofollow""} is as easy as ```java public static native JsArrayString getPortletInstances() /*-{ return $wnd.chatroomPortletInstances; }-*/; ``` Now all that's left is to iterate over portlet instances and create **Chatroom** instance for each. Of course feel free to use [my code](https://github.com/azzazzel/gwt-chatrooms-portlet/commit/4bc17ce9e8f9e23af5f49f7e9561be17e73f2efc){rel=""nofollow""} as example. **Now you can place as many GWT portlet instances as you wish on the same portal page!** If you want to test it, go to portlet's folder ant type ```text ant clean gwtc deploy ``` ## Generate Liferay services with ServiceBuilder The portlet needs to persist the chatrooms' messages in Liferay's database. Thus you need to have at least some services and persitence API on the server side. Fortunately with Liferay's [ServiceBuilder](http://www.liferay.com/documentation/liferay-portal/6.0/development/-/ai/service-builder){rel=""nofollow""} almost everything can be generated by providing a single XML file. [Here is the one I used](https://github.com/azzazzel/gwt-chatrooms-portlet/blob/63b77b5960a1f68e05f1bc602c3a9573a92aa3d7/docroot/WEB-INF/service.xml){rel=""nofollow""}. I'm not going to explain in details how [ServiceBuilder](http://www.liferay.com/documentation/liferay-portal/6.0/development/-/ai/service-builder){rel=""nofollow""} works\*(please see Liferay's documentation)\*. In case you are not really trying to learn but simply follow the instructions, here is the one-liner: Place the XML file in **WEB-INF** folder and then do ```text ant build-service ``` it will generate everything you need. The most important things to notice are the **Message** interface (represents chatroom message) and the **MessageLocalServiceUtil** class which provides convenient static methods for creating, storing and retrieving **Message** objects. By the way [this commit](https://github.com/azzazzel/gwt-chatrooms-portlet/commit/63b77b5960a1f68e05f1bc602c3a9573a92aa3d7){rel=""nofollow""} contains all the files generated by [ServiceBuilder](http://www.liferay.com/documentation/liferay-portal/6.0/development/-/ai/service-builder){rel=""nofollow""} in case you are curious. ## Implement the view and GWT RPC Finally it is time to get your hands dirty with some real GWT stuff. First you need to create standard [GWT RPC](http://code.google.com/intl/pl/webtoolkit/doc/latest/tutorial/RPC.html){rel=""nofollow""} service to save and receive messages. So create the **ChatroomService** and **ChatroomServiceAsync** interfaces in client code and **ChatroomServiceImpl** in server side code. They need to have 2 methods **saveMessage** and **getMessages**. In the implementation code of those methods in **ChatroomServiceImpl** you can call appropriate methods form **MessageLocalServiceUtil** or use Liferay's utility class **PortalUtil** to get access to other Liferay services *(for example to get the current user)*. [Here is what I did](https://github.com/azzazzel/gwt-chatrooms-portlet/blob/9a084048e203bbf3c6642c522ceb60c51e8a480b/docroot/WEB-INF/src/com/commsen/sample/portlet/chatrooms/server/ChatroomServiceImpl.java){rel=""nofollow""}. Then you need to code the view. It's really up to you how you design the front end but of course I'll use [my version of **Chatroom** class](https://github.com/azzazzel/gwt-chatrooms-portlet/blob/9a084048e203bbf3c6642c522ceb60c51e8a480b/docroot/WEB-INF/src/com/commsen/sample/portlet/chatrooms/client/Chatroom.java){rel=""nofollow""} to point out the key elements\*: - **sendMessageToServer** method uses **ChatroomService** to send user's message - **getMessages** method is continuously called by GWT timer to refresh the chatroom view - somewhat mysterious code at line 52 By now you may think that you are ready. Simply configure **ChatroomServiceImpl** servlet in **web.xml**, compile, deploy and enjoy. In fact if you do so, there will be unpleasant surprise. The problem with this standard GWT approach is that you will be calling the **ChatroomServiceImpl** servlet directly and not through the portal. This means the portal will never have a chance to do it's magic. This means non of it's service classes will be available for the servlet. This means you'll see a nice **NoClassDefFoundError** in your server's logs. Luckily since version 4.3.x Liferay has built in solution for this problem. It's called [PortalDelegateServlet](http://longgoldenears.blogspot.com/2008/03/portaldelegateservlet-servlet-session.html){rel=""nofollow""}. I'm oversimplifying the concept here but basically it allows you to define a servlet in portal's context. There is special delegate servlet, mapped at **/delegate** location. Liferay extensions can configure sub-contexts to redirect to their own servlets after the portal is done with the magic. Having this in mind there are 2 more things you need to do: - configure your portlet to use **PortalDelegateServlet** - change the address of your servlet in GWT's front-end code For the first one have a look at [how I have done it](https://github.com/azzazzel/gwt-chatrooms-portlet/blob/9a084048e203bbf3c6642c522ceb60c51e8a480b/docroot/WEB-INF/web.xml){rel=""nofollow""} and for the second one ... well that is the "somewhat mysterious code at line 52" mentioned above. Of course complete list of changes described in this section is in my [final commit](https://github.com/azzazzel/gwt-chatrooms-portlet/commit/9a084048e203bbf3c6642c522ceb60c51e8a480b){rel=""nofollow""}. And this is it. Compile, deploy, enjoy! # Liferay multi-device extension **UPDATE: The extension described here targets Liferay 6.0. It was contributed to Liferay and is [available out of the box since Liferay 6.1](https://MilenDyankov.com/blog/2012/05/mobile_device_detection_in_liferay_61)** Almost every portal related RFI/RFP my company has received in the last couple of years contained some requirements about mobile version. Fortunately the times when every device had it's own idea of how web content should be served are gone. Nowdays we can afford to pretty much ignore [WML](http://en.wikipedia.org/wiki/Wireless_Markup_Language){rel=""nofollow""}, [C-HTML](http://en.wikipedia.org/wiki/CHTML){rel=""nofollow""}, ... as almost any modern device understands at least [XHTML Mobile Profile](http://en.wikipedia.org/wiki/XHTML_Mobile_Profile){rel=""nofollow""}. However this does not always mean there can be one mobile version for all. Here are some typical requirements: - make a dedicated version for iPhone (imitate iPhone interface to make it look like native application) - allow to switch between mobile and desktop version if device is smartphone - provide alternative input methods if device does not have QWERTY keyboard - design dedicated version for tablets I have spent some time thinking about how to address this issues with [Liferay Portal](http://www.liferay.com){rel=""nofollow""}. Having some experience with [WURFL](http://wurfl.sourceforge.net){rel=""nofollow""}, [Volantice](http://www.volantis.com/){rel=""nofollow""} and designing web applications for mobile devices in general, I thought it would be great if I could dynamically change Liferay's look and feel based on device capabilities. And this is how [Liferay multi-device extension](http://sourceforge.net/projects/liferaymultidev/){rel=""nofollow""} was born. Actually now there are 3 Liferay plug-ins which work together to deliver this functionality: - **multi-device-ext plugin** ({rel=""nofollow""}) is the core plug-in. It provides the look and feel change logic, generic data model and "extension points" for other plug-ins which deliver things like device recognition and rule definition. It does so by employing Liferay's internal bus and can dynamically switch to new implementation when compatible plug-in is deployed. If you know how to replace Lucene with Solr, you know what I'm talking about. - **wurfl-web** ({rel=""nofollow""}) plug-in delivers device recognition based on WURFL. It contains WURFL API but does not contain WURFLD DB and patches. By default it expects to find the database in `${liferay.home}/wurfl/wurfl-latest.zip` however you may change this in portal-ext.properties: ```text # Wurfl's main devices file wurfl.main=${liferay.home}/wurfl/wurfl-latest.zip # Wurfl's patch files wurfl.patches= ``` - **device-rules-hook** ({rel=""nofollow""}) extends Liferay's look and feel management interface by adding additional tab "Device Rules". At the moment rules can be based on device's brand, model, operating system, browser and pointing method as well as whether the device is tablet, has QWERTY keyboard Here is how it works: :iframe{allowFullScreen="true" frameBorder="0" height="390" src="http://www.youtube.com/embed/2CvY4eLWMHQ" title="YouTube video player" width="640"} The plug-ins are not yet in Liferay community plug-ins repository. I could'n figure out how to upload the EXT plugin and the other two make no sense without it. You can download plug-ins from here: {rel=""nofollow""} or get the source code from {rel=""nofollow""} and build them yoursef. If you do so, please let me know what you think. # Liferay GWT portlet - replacing GWT-RPC with JSON This is a continuation of my previous post [Liferay GWT portlet - how to make it "instanceable" and use GWT RPC](https://MilenDyankov.com/blog/2011/01/liferay_gwt_portlet_how_to_make_it_instanceable_and_use_gwt_rpc). The approach described there uses Liferay specific functionality called [PortalDelegateServlet](http://longgoldenears.blogspot.com/2008/03/portaldelegateservlet-servlet-session.html){rel=""nofollow""}. This way one can easily use GWT RPC which somewhat simplifies client-server communication. However if you need to develop a JSR 286 portlet you need a more standard compatible way of doing AJAX calls. For this reason JSR 286 defines `serverResource` method and this post will show how to refactor the code to replace GWT RPC calls with exchanging JSON messages using serverResource method. ## Let GWT know the prtlet URLs First thing to do is to tell GWT what are the proper URLs to call the portlet. Therefore creating a `Chatroom` instance based on portlet id only, is no longer enough. To overcome this you need to provide a JavaScript object holding portlet URLs. *I, for example, have called it `ChatroomPortlet` and it's defined in [chatrooms.js](https://github.com/azzazzel/gwt-chatrooms-portlet/blob/df808f66e36b1e0257c3d31bcd869779960f08a8/docroot/js/chatrooms.js){rel=""nofollow""} file.* Then, in [view.jsp](https://github.com/azzazzel/gwt-chatrooms-portlet/blob/df808f66e36b1e0257c3d31bcd869779960f08a8/docroot/view.jsp){rel=""nofollow""}, create and store that object instead of portlet id. *Of course for the purpose of this example only `resourceURL` is needed but in a real world scenario you'll probably also need `renderURL` and `actionURL`.* To map this JavaScript object to GWT class create JSNI class [ChatroomJsObject](https://github.com/azzazzel/gwt-chatrooms-portlet/blob/df808f66e36b1e0257c3d31bcd869779960f08a8/docroot/WEB-INF/src/com/commsen/sample/portlet/chatrooms/client/ChatroomJsObject.java){rel=""nofollow""}. Next you need to modify [Chatroom](https://github.com/azzazzel/gwt-chatrooms-portlet/blob/df808f66e36b1e0257c3d31bcd869779960f08a8/docroot/WEB-INF/src/com/commsen/sample/portlet/chatrooms/client/Chatroom.java){rel=""nofollow""}'s constructor to accept `ChatroomJsObject` instead of `String` representing portlet id. Of course this reflects how [GWTEntryPoint](https://github.com/azzazzel/gwt-chatrooms-portlet/blob/df808f66e36b1e0257c3d31bcd869779960f08a8/docroot/WEB-INF/src/com/commsen/sample/portlet/chatrooms/client/GWTEntryPoint.java){rel=""nofollow""} creates `Chatroom` instances. Have a look at [my commit](https://github.com/azzazzel/gwt-chatrooms-portlet/commit/df808f66e36b1e0257c3d31bcd869779960f08a8){rel=""nofollow""} to see what has changed. ## Create the JSR 286 portlet Now you need to write a portlet and implement `serveResource` method. Basically the method contains the same logic that used to be in [ChatroomServiceImpl](https://github.com/azzazzel/gwt-chatrooms-portlet/blob/9a084048e203bbf3c6642c522ceb60c51e8a480b/docroot/WEB-INF/src/com/commsen/sample/portlet/chatrooms/server/ChatroomServiceImpl.java){rel=""nofollow""}. The only difference is that now it gets its input form JSON object and responds with JSON object. The portlet code is available [here](https://github.com/azzazzel/gwt-chatrooms-portlet/blob/7da4cef3a7b9978da60c553d1704778ad280af30/docroot/WEB-INF/src/com/commsen/sample/portlet/chatrooms/server/ChatroomPortlet.java){rel=""nofollow""}. Of course don't forget to replace the default `MVCPortlet` with your own in [portlet.xml](https://github.com/azzazzel/gwt-chatrooms-portlet/blob/7da4cef3a7b9978da60c553d1704778ad280af30/docroot/WEB-INF/portlet.xml){rel=""nofollow""} Again [there is commit](https://github.com/azzazzel/gwt-chatrooms-portlet/commit/7da4cef3a7b9978da60c553d1704778ad280af30){rel=""nofollow""} which does above modifications, so you can check what has changed. ## Update GWT client-server calls Having the portlet ready, it's time to change the GWT code to send and receive JSON to `resourceURL` instead of using GWT RPC. For this to work you need to add 2 GWT modules to [Chatrooms.gwt.xml](https://github.com/azzazzel/gwt-chatrooms-portlet/blob/03a0e357411efae82fdd8160620b81c5b8e2b64b/docroot/WEB-INF/src/com/commsen/sample/portlet/chatrooms/Chatrooms.gwt.xml){rel=""nofollow""}: ```text ``` To be able to convert JSON response to GWT class you'll have to provide another JSNI class [ChatroomMessageJsObject](https://github.com/azzazzel/gwt-chatrooms-portlet/blob/03a0e357411efae82fdd8160620b81c5b8e2b64b/docroot/WEB-INF/src/com/commsen/sample/portlet/chatrooms/client/ChatroomMessageJsObject.java){rel=""nofollow""}. Finally the `Chatroom` class itself needs to be updated: - replace the body of `sendMessageToServer` method to create JSON object and send it to the portlet bu using RequestBuilder - replace the body of `getMessages` method to covert JSON object from response to list of ChatroomMessageJsObject to be displayed. - convert `lastMessageTime` form `Date` to `long` as there are some issues with passing dates in JSON As usual you can refer to [my commit](https://github.com/azzazzel/gwt-chatrooms-portlet/commit/03a0e357411efae82fdd8160620b81c5b8e2b64b){rel=""nofollow""} to get an idea what and how has changed. That's it. You are ready. Optionally you can do some cleanup by removing unused classes [like I did](https://github.com/azzazzel/gwt-chatrooms-portlet/commit/aa6b4a76b195316446df10e21b20d905e4ba77be){rel=""nofollow""}. # Liferay - preserve GWT portlet state between reloads One of the problems with GWT *(which is even more noticeable in portal environment)* is preserving it's state between page reloads. In a GWT-only application *(or single portlet on the page case)* one can give user no other option but using only GWT controls to practically avoid page reloads. In most cases however this is not really possible nor wise thing to do. In portlet environments in particular, reloading the page is a very commmon thing to do, giving all portlets a chance to refresh their content after some action has taken place. The thing is, GWT portlets will, by default, render their initial state, which may not be what user expects. For example, consider the GWT `Chatroom` portlet I was using in my previous posts [Liferay GWT portlet - how to make it "instanceable" and use GWT RPC](https://MilenDyankov.com/blog/2011/01/liferay_gwt_portlet_how_to_make_it_instanceable_and_use_gwt_rpc) and [Liferay GWT portlet - replacing GWT-RPC with JSON](https://MilenDyankov.com/blog/2011/03/liferay_gwt_portlet_replacing_gwt-rpc_with_json). Imagine user has entered a chatroom. Then she clicks on some other portlet on the page. The page is reloaded and `Chatroom` portlet returns to it's initial state. The user will have to enter the room again every time she clicks on another portlet. Let's see how this can be fixed. Many people think about GWT state as a summary of the states of all used GWT widgets (text fields, combo boxes, grids, tabs, ...). Instead I prefer to think in terms of application or "business logic" states. If you change the point of view, it may turn around not all GUI components contain significant information that must be saved and restored. For example it may be not so important to have particular tab selected or value of particular text box updated. In case of `Chatroom` portlet, there are 2 states: - `initial state` - the user is not in a chatroom (she may never entered one or just left one) - `chatroom entered` - the user is in a chatroom In fact this is made very clear in the code by providing 2 methods [displayInitialState](https://github.com/azzazzel/gwt-chatrooms-portlet/blob/master/docroot/WEB-INF/src/com/commsen/sample/portlet/chatrooms/client/Chatroom.java#L180){rel=""nofollow""} and [displayChatroomState](https://github.com/azzazzel/gwt-chatrooms-portlet/blob/master/docroot/WEB-INF/src/com/commsen/sample/portlet/chatrooms/client/Chatroom.java#L167){rel=""nofollow""} responsible for rendering appropriate GUI elements in particular states. Up until now during `Chatroom` initialization the [displayInitialState](https://github.com/azzazzel/gwt-chatrooms-portlet/blob/master/docroot/WEB-INF/src/com/commsen/sample/portlet/chatrooms/client/Chatroom.java#L180){rel=""nofollow""} method was called. To fix the above problem the initialization has to be changed to call appropriate method depending on what is the current state. The question is where *(on the client side)* one can store portlet state information so it survives page reloads. The obvious answer is "cookies". However it has some drawbacks *(the major one being the fact that they may be unsupported/disabled in some browsers)*. Fortunately back in 2008 [Thomas Frank](http://www.thomasfrank.se/about.html){rel=""nofollow""} wrote and made public a very clever JavaScript library called [sessvars.js](http://www.thomasfrank.se/sessionvars.html){rel=""nofollow""} which uses `widow.name` property to store information that need to survive page reloads. In order to use it in the portlet it has to be added to the page and this is what [this commit](https://github.com/azzazzel/gwt-chatrooms-portlet/commit/5fc9f5914f7d38e354b6398616d03528ff36d8fd){rel=""nofollow""} does. Now in order to actually save and load portlet state, the `ChatroomPortlet` object (defined in [chatrooms.js](https://github.com/azzazzel/gwt-chatrooms-portlet/blob/eebb9c725b46a937c948977f3f47a703d34884e1/docroot/js/chatrooms.js){rel=""nofollow""}) has to be extended with 2 new methods: `setState` and `getState`. These methods respectively write to or read from a map\*(key being portlet id and value portlet state)\* which thanks to [sessvars.js](http://www.thomasfrank.se/sessionvars.html){rel=""nofollow""} can survive page reloads. Having this prepared, the `Chatroom` class can be refactored to render portlet differently depending on it's state. This can be combined with GWT's history mechanism to handle clicks on browser's back and forward buttons. [This is how modified the code looks like](https://github.com/azzazzel/gwt-chatrooms-portlet/blob/eebb9c725b46a937c948977f3f47a703d34884e1/docroot/WEB-INF/src/com/commsen/sample/portlet/chatrooms/client/Chatroom.java){rel=""nofollow""}. Basically the steps were as follows: - provide `saveState` method to both save the current state and add history token - call `saveState` method every time portlet state changes - provide `handleState` method to render portlet UI according to current state - make `Chatroom` class implement `ValueChangeHandler` and provide `onValueChange` method to handle history tokens. - provide `getStateFromToken` method to retrieve the portlet state from history token. All of the above mentioned changes are in [this commit](https://github.com/azzazzel/gwt-chatrooms-portlet/commit/eebb9c725b46a937c948977f3f47a703d34884e1){rel=""nofollow""}. Feel free to explore what has changed and how. # Simple mobile device emulator in Firefox After my "Pluggable mobile device detection" presentation during [Liferay Europe Symposium](http://www.liferay.com/events/liferay-symposiums/europe-2011/agenda){rel=""nofollow""} a lot of people asked about the mobile device emulator I was using. The truth is, it's not a real "emulator" but a simple combination of html page and a Firefox user script. However, it does the trick and for most people seems to be good enough (at least for a start). So, I made a promise to share it and finally found the time to blog about it. But before I go into details, here is a short video which demonstrates what it does (for those of you who didn't attend Liferay Europe Symposium and have no idea what I'm writing about): :iframe{allowFullScreen="true" frameBorder="0" height="315" src="http://www.youtube.com/embed/__gvtlJ-KLI" width="560"} Now the details. First, make sure you have recent Firefox version installed. You'll also need one of the following Firefox extensions: - [Scriptish](http://scriptish.org/){rel=""nofollow""} - [Greasemonkey](https://addons.mozilla.org/pl/firefox/addon/greasemonkey/){rel=""nofollow""} *NOTE: I've only tested it with Scriptish but it should work with Greasemonkey as well.* Having this in place you are ready to "install" the emulator. It's source code is available on [GitHub](https://github.com/azzazzel/phone_emulator){rel=""nofollow""}. The `page` folder contains the host page together with required js, css and image files. I personally have [Apache](http://httpd.apache.org/){rel=""nofollow""} running locally on my machine so I have these files in its `` and access the emulator via {rel=""nofollow""}. However feel free to use any HTTP server you like (it may even work if you simply put it into a folder and access it via `file:///path/to/folder/phone_emulator.html`). The HTML file provides the GUI: - the URL text box - the device tabs (it uses [tabifier library](http://www.barelyfitz.com/projects/tabber/){rel=""nofollow""} to create the tabs from `div` elements) - the background image and the `iframe` element for each device If you wish to add more devices simply - add the following code replacing `${...}` with appropriate values ```html
        ``` - add appropriate CSS styles to display the background image and position the iframe Finally, you need to install the [user script](https://github.com/azzazzel/phone_emulator/raw/master/userscript/phone_emulator.user.js){rel=""nofollow""}. Its purpose is to omit "same origin" policy and load requested URL in every available `iframe`, each time changing the `User-Agent` header appropriately. The scripts is by default hooked to {rel=""nofollow""} location and will not run if you have placed the HTML file somewhere else. However you may edit the ```javascript // @include http://localhost/phone_emulator.html* ``` line and provide one or more different locations. This is it! Just type {rel=""nofollow""} in Firefox and you should have the emulator runnig. To make sure you have installed it correctly, check the little green [Scriptish](http://scriptish.org/){rel=""nofollow""} icon in Firefox's status bar. It should say that 1 user script is enabled. **Disclaimer**: *This is not a real emulator and should not be used as such. It was NOT tested and is known to have at least the following issues/limitations:* - the rendering is done by Firefox which may support a lot more features then actual device's browser - it only emulates the page in the provided location. If you click on a link it the emulator it will be loadded using standard Firefox `User-Agent` header! - some Javascripts (particularly Google APIs) and/or CSS may cause conflicts and not display properly You've been warned, use it on your own risk ;) And of course if you feel you can make it better, extend it or build something else on top of it, go ahead and do so (just let me know). # Liferay Beginner's Guide - review ![](https://www.packtpub.com/sites/default/files/imagecache/productview/7003OS_Liferay%20Beginner's%20Guidecov_Low.jpg){style="float:right; margin: 10px"} As I promised a few weeks ago, in this post I'll share my thoughts about "Liferay Beginner's Guide" book. As with earlier reviews, don't expect any judgments, recommendations or generalizations. Those are to be made by you. I'll only concentrate on what I found interesting (or boring) and worth mentioning (for one reason or another). So let me try to summarize over 350 pages in a few lines. The first thing to mention is that you will not have to write a single line of code (apart from configuration). This book seems to be meant for ..., hmm. I was about to write "administrators" but thats not the right term here. Please help me find the English term for a adventurous webmaster who goes like - download, make a couple of clicks to configure and then "go live". In another words, I have the impression the authors goal was to show that Liferay is just as easy to install and configure as all these popular CMS systems written in PHP. Whether that's the truth or not, you'll have to decide for yourself. The first 60 pages will teach you to ... install. No, sure it's not that hard to install Liferay. The book will teach you to install everything you (may eventually) need - Java, MySQL, OpenOffice, and then a combination of Liferay and a few of the major application servers out there (like JBoss, Glassfish, Weblogic). Don't expect any detailed description or explanation of what's where in configuration files. The book goes like "copy the file", "click here" and "type this there". If this procedure fails for some reason, you'll have to look for help somewhere else. Also the whole installation part assumes your OS is Windows. As most of you probably still use this "state of the art" OS, the approach is probably OK if you install locally to give it try. But once you decide to go on-line, you'll have to either pay the price for Windows hosting or find another book to tell you how to do the same installation and configuration in some "better suited for the net" OS. The next 200 pages will walk you through portal configuration as well as site and content management. As usual, some things are better explained then other, but if you are a novice you'll get the idea of how Liferay was meant to work. By the way, in case you want my advice, as soon as you think you have figured it all out, go read the chapter again. If the impression remains, switch to more advanced book. No, don't get me wrong, it's not that there is something wrong with the book. Just keep in mind, it was meant to be for beginners and some Liferay features (permissions for example) deserve a book of their own. The most odd (at least for me) part of the book is in the next 60 or so pages and attempts to explain how to set up an on-line shop. I have to admit, the authors do their best to explain the Liferay's out of the box features in this context. But the question that still bothers me is "Why would beginner be interested in setting up on-line store with Liferay?" I mean, are there chapters about setting up an on-line shops in any "A popular CMS written in PHP beginner's guide" book? I have no idea what was the authors' intention. Perhaps, showing up that Liferay is more than simply CMS. Well it is! It's a portal! But it's definitely not an e-commerce suite! The fact that out of the box you get a couple of portlets which have some basic product catalog and shopping cart functionality, does not make it such. Setting up an on-line store is NOT about having a shopping cart on the web site. It's rather complex subject that requires good understating of security and at least some understanding of what terms like "Product bundling", "SKU", "Price lists", "Upselling", "Cross-selling", etc mean. Sorry for emphasizing this but please trust me, even if you memorize the whole chapter, you are NOT ready to go live with your first on-line store. OK, this post became too long so time to sum it up. The book is definitely for beginners. But actually it's a good thing, as I don't recall any other Liferay book targeting this audience. So if you are trying to figure out what is Liferay, what it offers and where you are supposed to click, then it may be an useful guide. Of course, It will not tell you the whole story, but at least it will answer most of the questions a beginner asks. It's very likely the book will significantly reduce the time you spend googling for answers. It may even save me and many other people some time, as we would eventually spend less time answering basic questions on Liferay forums ;). # Liferay Portal Systems Development - review ![](https://www.packtpub.com/sites/default/files/imagecache/productview/5986OS_Liferay%20Portal%20System%20Development_Frontcover.jpg){style="float:right; margin: 10px"} OK, let me cut the analogy here and try to provide some (hopefully constructive) criticism and point out some cool Liferay features this book explains. In this order ;) #### constructive criticism As you may have already guessed, I found the book kind of hard to read. I've read almost all Liferay books out there but this one was the strangest in terms of language, chapter organization, consistency of knowledge, ... - My first impression (while reading about ServiceBuilder) was that the content is taken form Liferay's training slides. Fortunately it turned out that the book tries to provide more details and explain some concepts a bit better. - Another impression (which remained till the end) was that some of the sections were created as copy/paste of the previous sections by only changing some key information. There are sentences (or even paragraphs) which simply does not fit in the context. For example a sentence starting with *"As you can see ..."* without any code sample, diagram or anything that can actually let you see anything. - The book is full of code examples but I think a lot of them are actually useless. For example: ```java public ClassLoaderProxy(Object obj, ClassLoader classLoader){} // see details in ClassLoaderProxy.java private String _className; ``` > As shown in the preceding code, the method invoke uses a class named MethodHandler, which implements the interface Serializable. :brI guess method invoke got replaced by `// see details in ClassLoaderProxy.java`:br Or another example: > When the staging is disabled, either local live or remote live, the portal will remove all the properties from the field typeSettings of the table Group\_. How come? The following code is a snippet from the method disableStaging of the class StagingImpl: ```java GroupLocalServiceUtil.updateGroup(liveGroup.getGroupId(), typeSettingsProperties.toString()) ``` :brIf you understand how this single line of code removes *"all the properties from the field typeSettings of the table Group\_"*, the chances are you don't need to read this book. - I guess the book was written before 6.1 was actually released and thus some important changes in Liferay's 6.1 versions are not reflected by the book. The most important is the fact that tunnel-web is gone in 6.1. It was merged to Liferay core and is no longer distributed as separate web application. However the book still mentions it in the sections about WebDAV, remote services and remote staging. If this is your main area of interest then you'll probably need to look for other sources to get familiar with the changes in 6.1. - The book is full of tables. It would probably be safe to write that tables make between 1/3 and 1/2 of some chapters' content. Unfortunately the value added is negligible comparing to the space they take. Personally I would resign with most of them (perhaps making the book thinner of about 100 pages). For example there is a full page table describing *"other entities and their definitions, such as, JournalArticleImage, JournalStructure, JournalTemplate, JournalFeed, and JournalContentSearch"*. Assuming the reader understands the pattern `ServiceBuilder` uses to generate files and methods (described in the first chapter) this table provides nothing but names. Even the most valuable column "description" is not able to defend the usage of tables as most of the time it contains the value of the first column having capital letters replaced by space and appropriate lowercase letter. Like this:
        object name ... other columns ... description
        PortalCache ... Portal cache
        PortalCacheManager ... Portal cache manager
        SingleVMPool ... Simple VM pool
        - There are quite a few diagrams in the book. Fortunately most of them are entity diagrams which are rather self explanatory. The action/flow diagrams however are somewhat confusing. In my opinion they are not strict UML diagrams and it's hard to figure out whether an arrow means *"extends"*;, *"implements"*, *"uses"* or something else. Particularly in LAR and staging sections, depending on how you interpret the arrows you may be surprised to discover the diagram is showing exactly the opposite of what is explained next to it. Now, I understand, all of the above may sound as petty malices. However most of them can be fixed in the next edition and that is the main reason to point them out. What is hard to fix however (at least not without rewriting the whole book) is the fact that the book, in my opinion, does not keep the promise it makes in the first chapter > This book is going to show you how to develop portal systems via a real example – knowledge base management. This statement empowered by (page and a half long) list of requirements made me think, I was going to be walked through the process of building my own knowledge base solution. I mean starting from scratch and then adding features step by step by employing different APIs, tools, concepts, etc. Unfortunately the approach turned out to be a completely different. The chapters and sections are merely informing the reader that there is something in the portal that can be used (one can guess how it's supposed to help fulfill a particular requirement). For example let me show you how scheduling is described in *"Scheduling and messaging"* chapter : - three paragraphs of what scheduling, Quartz and JMS are respectively - one paragraph (5 sentences) about `scheduler.enabled` and `scheduler.job.name.max.length` properties - one table with interface names related to scheduler - one table with service names related to scheduler - one table with spring beans names related to scheduler - couple of paragraphs about some scheduler clustering properties - one table with interface names related to scheduler clustering That's it. Not a single word of how to use it or build your own schedulers. Not a single usage example. Not even an information about how this relates to the knowledge base management system we are building. Unfortunately many sections follow this pattern and some (like *"Mobile device detectors"* or *"Securing users' information"* for example) contain even less information. #### some cool Liferay features this book talks about Despite of the criticism above, one can still learn a lot from this book. I've been working with Liferay for a several years and still was able to learn a few new things. Assuming you're a big boy/girl who does not need to be hand-holded and you don't mind digging into 3rd parties source codes to explore things by yourself - you may find this book a pretty good reference. In fact you may discover Liferay has some features you never thought you'll find in a portal. So let me point out some of the things that may make this book worth buying : - The ServiceBuilder chapter is quite good. Besides the basics it will also explain - what are reserved table and column names and how to add your own - how to handle ID fields and what types of identifiers one can use - how to extend ServiceBuilder to support, for example, BigDecimal - how to configure "fast development" so you don't have to manually redeploy all the time - The Generic MVC portlets chapter also explains some useful but less known features like: - now AJAXable portlets are loaded and what render weight is used for - how to use direct JSP servlet to bypass FilterChain for specific resources - how to use model hints to fine tune entity fields - how to use dynamic and custom queries - Other sections that only give you the basics but point out an interesting subject once you start looking for more information by yourself, will tell you about: - Liferay sandbox and sandbox deployer - a cool new feature in 6.1 - Class loader proxy and how to share plugin services. - How text is extracted from binary files (like DOC, XLS, PDF, ..) and what OCR tools are or can be used - How to use (and add your own) tokens in journal articles - How content indexing, faceted search and open search work Of course this is by far not the complete list. It's just an attempt to extract the topics which can draw the attention of an average Liferay developer. But the truth is, unless you are a "Liferay Legend" *(if you use Liferay forums you know what I mean)* you'll probably learn something new from this book. And for those of you who started working with Liferay not so long ago, there may be even some big surprises waiting around the corner. As usual, whether the book is good or bad you'll have to decide by yourself. I was just trying to get you prepared for what is inside. Hopefully you can adjust your expectations to avoid disappointments. Also, please note, this review is subjective and influenced by what I already know about Liferay and what is my vision of how a book should look like. So before you make any decision, please read the [sample chapter](http://www.packtpub.com/sites/default/files/5986OS-Chapter-3-Generic-MVC-Portlets.pdf?utm_source=packtpub&utm_medium=free&utm_campaign=pdf){rel=""nofollow""} and look for other opinions. # Mobile device detection in Liferay 6.1 I'm still getting a lot of questions about how to use the [multi-device extension](https://MilenDyankov.com/blog/2011/03/liferay_multidevice_extension) in Liferay 6.1. The answer is, **you don't have to**! The code was contributed to Liferay and it's now available OOTB in Liferay 6.1. The following comparison table will give you a better idea of what went where:
         Feature Liferay 6.0  Liferay 6.1
        look and feel change logic, generic data model and "extension points" for other plug-ins. multi-device-ext plugin integrated into Liferay's core. No need to install additional plug-in
        Device recognition based on WURFL wurfl-web 6.0.5.x plugin (does not contain WURFLD DB) wurfl-web 6.1.0.x (available under AGPL license, due to the fact WURFL itself switched to AGPL, as part of Liferay's official plug-ins. It contains the WURFL's database!)
        Building device rules and applying actions to matched rules device-rules-hook-6.0.5.x plugin Integrated into Liferay's core. No need to install additional plug-in (Provides somewhat different approach for managing rules and rule groups. Rules conditions simplified/limited to OS chooser and "is tablet" combo box.)
        *So, to use device detection in Liferay 6.1, all you need to do is download and install the official wurfl-web plugin!* If you get exception like this : ```text SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder". SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details. Exception in thread "liferay/hot_deploy-1" java.lang.NoClassDefFoundError: org/slf4j/impl/StaticLoggerBinder at org.slf4j.LoggerFactory.getSingleton(LoggerFactory.java:230) at org.slf4j.LoggerFactory.bind(LoggerFactory.java:121) at org.slf4j.LoggerFactory.performInitialization(LoggerFactory.java:112) at org.slf4j.LoggerFactory.getILoggerFactory(LoggerFactory.java:275) at org.slf4j.LoggerFactory.getLogger(LoggerFactory.java:248) at org.slf4j.LoggerFactory.getLogger(LoggerFactory.java:261) at net.sourceforge.wurfl.core.resource.XMLResource.(XMLResource.java:59) at com.liferay.portal.mobile.device.wurfl.WURFLHolderImpl.getWURFLDatabase(WURFLHolderImpl.java:140) at com.liferay.portal.mobile.device.wurfl.WURFLHolderImpl.initialize(WURFLHolderImpl.java:73) at com.liferay.portal.mobile.device.wurfl.messaging.WURFLDeploymentMessageListener.doReceive(WURFLDeploymentMessageListener.java:52) at com.liferay.portal.kernel.messaging.BaseMessageListener.receive(BaseMessageListener.java:25) at com.liferay.portal.kernel.messaging.InvokerMessageListener.receive(InvokerMessageListener.java:65) at com.liferay.portal.kernel.messaging.SerialDestination$1.run(SerialDestination.java:101) at com.liferay.portal.kernel.concurrent.ThreadPoolExecutor$WorkerTask._runTask(ThreadPoolExecutor.java:669) at com.liferay.portal.kernel.concurrent.ThreadPoolExecutor$WorkerTask.run(ThreadPoolExecutor.java:580) at java.lang.Thread.run(Thread.java:662) Caused by: java.lang.ClassNotFoundException: org.slf4j.impl.StaticLoggerBinder at org.apache.catalina.loader.WebappClassLoader.loadClass(WebappClassLoader.java:1688) at org.apache.catalina.loader.WebappClassLoader.loadClass(WebappClassLoader.java:1533) ... 16 more ``` Then: - download `slf4j` from {rel=""nofollow""} - unzip it and copy `slf4j-log4j12.jar` to ` “@[nbartlett](https://twitter.com/nbartlett) New blog post: No Solution for Complexity? > [njbartlett.name/2013/02/04/no-…](http://t.co/oZEdelzs "http://njbartlett.name/2013/02/04/no-solution-for-complexity.html") > [#OSGi](https://twitter.com/search/%23OSGi)” > > — Raymond Augé (@rotty3000) > [February 6, 2013](https://twitter.com/rotty3000/status/298968466914942977) which leads to a [this article](http://njbartlett.name/2013/02/04/no-solution-for-complexity.html){rel=""nofollow""}. I read it quickly on my cell phone and got the following impression **"The solution for Complexity is modularity. The way you properly do modularity is OSGi. So OSGi is the solution for complexity!"** Without thinking too much I responded: > @[rotty3000](https://twitter.com/rotty3000) As much as I like > [#OSGi](https://twitter.com/search/%23OSGi), I don't like articles that suggest it's the universal solution to all complexity issues. > > — Milen Dyankov (@milendyankov) > [February 6, 2013](https://twitter.com/milendyankov/status/299057649595580416) Which caused an immediate reaction: > @[milendyankov](https://twitter.com/milendyankov) I didn't suggest that at all /cc @[rotty3000](https://twitter.com/rotty3000) > > — Neil Bartlett (@nbartlett) > [February 6, 2013](https://twitter.com/nbartlett/status/299062113996001282) We exchanged a few more tweets but as Twitter is not the best platform for explaining what one have in mind, I thought I'll write it here. Let me start by stating that now that I have read the article again, I must admit my first impression wasn't exactly correct. I now understand the intention and generally agree with it. However the article takes a few shortcuts which I would like to argue with: > If only there were a way to create "firewalls" between different parts of a large system, so that we could be absolutely sure that the functionality within each firewall cannot break merely from adding new functionality outside it. Then we could know precisely the scope of any change, and test only the things that can potentially break rather than the entire universe. This would be true given than all the "firewalls" are completely independent of each other. Something I have never seen so far in any of the complex systems I've been working on in the last 10 years. And even if it was the case, you will need to take care of the "glue code", provide communication interfaces resistible to changes, ensure data consistency between modules, ... I mean, think about SOA, the most modular approach I can think of. Can you "be **absolutely** sure" that when you change one service it will not brake any other? What about changing the data model for example? What about missing service? My experience tells me there is no such thing as "*absolutely* sure"! Need more visual example? OK, think about setting up a network. Each switch, router, ... is separate module placed in a rack, which in turn is also a module, ... Does this reduces the complexity of your network? Yes? Well, [think again](http://www.itdisasters.com/2009/10/15/can-you-find-the-network/){rel=""nofollow""}: ![](http://www.itdisasters.com/wp-content/uploads/2009/10/wire_wrap1.png) Modularity is a good thing, no doubt about it, but only when it makes sense for particular project and is done right! Sometimes it does solve complexity issues but sometimes it just moves them to different layer. And it has it's price. > Nevertheless there is one technology that is mature, well proven and has stood the test of time: OSGi. :br > . . . :br > Look it up, and perhaps you could save your bank from making headlines for all the wrong reasons. Absolutely agree with the first part. However will it really save your bank? Many believe OSGi is way too complex by itself. If fact I think this (whether true or not) is the main reason why OSGi is still not as popular and widely used as I would like it to be. May be Neil is right: > @[milendyankov](https://twitter.com/milendyankov) And I believe those who find OSGi complex are mistaken about the true source of that complexity. > > — Neil Bartlett (@nbartlett) > [February 6, 2013](https://twitter.com/nbartlett/status/299082760658771969) but do the test yourself. Pick randomly 10 or 20 average developers and ask them to explain to you how Java class loading works. Or at least simply ask what is causing this message: ```text Exception in thread "main" java.lang.ClassCastException: com.my.company.MyClass cannot be cast to com.my.company.MyClass ``` and see how many correct answers you get (in case you don't know [read this](http://plumbr.eu/blog/cryptic-error-messages-in-java){rel=""nofollow""}). Got many correct answers? Good for you, you have some really good developers around. Now try to ask the same but in context of JBoss, Glassfish, WebSphere, ... It becomes more complex right? Why?Among the other things, because it is modular. Jars, wars, ears, deployment contexts, ... Will OSGi reduce this complexity and save your bank? Well, it may or may not, depends on what your use case is. ### Don't get me wrong, OSGi is a great platform! It's getting better and better with each release. And much easier to use ([Bndtools](http://bndtools.org){rel=""nofollow""}, [iPOJO](http://felix.apache.org/site/apache-felix-ipojo.html){rel=""nofollow""}, [Blueprint](http://wiki.osgi.org/wiki/Blueprint){rel=""nofollow""}, ...) and much more user/administrator friendly ([Karaf](http://karaf.apache.org){rel=""nofollow""}, [Virgo](http://www.eclipse.org/virgo/){rel=""nofollow""}, ... ) then it used to be some years ago. And while it will not automatically solve all your modularity problems and reduce the complexity of your application it may indeed be very helpful. # Cross blogging It's been a while since my last post here. Apart from the traditional 'no time' excuse, there is one more. Since I joined [Liferay](http://liferay.com){rel=""nofollow""} last year, my Liferay related blogs are now on Liferay's website: [](http://www.liferay.com/web/milen.dyankov/blog){rel=""nofollow""}. I don't think cross blogging is a good idea, so I didn't want copy them here as well. However some people have asked to at least add here a link to my other blogs whenever I publish something new. So here are the links for the ones that are already out there: - [Liferay source code history - a piece of art](http://www.liferay.com/web/milen.dyankov/blog/-/blogs/liferay-source-code-history-a-piece-of-art){rel=""nofollow""} - Shows what it takes to build a product like Liferay in a very visual way - [The power (user) is back](http://www.liferay.com/web/milen.dyankov/blog/-/blogs/the-power-user-is-back){rel=""nofollow""} - An attempt to explain how User and PowerUser roles differ and what they are used for - [Mobile Device Recognition beyond the UI](http://www.liferay.com/web/milen.dyankov/blog/-/blogs/mobile-device-recognition-beyond-the-ui){rel=""nofollow""} - Revealing some not very well know features of Liferay's mobile device recognition feature From now on, I'll try to announce here my Liferay blogs. I also have some thought to share on different technologies (like [Karaf](http://karaf.apache.org/){rel=""nofollow""}, [OrientDB](http://www.orientdb.org/){rel=""nofollow""} and [AngularJs](http://angularjs.org/){rel=""nofollow""} to mention a few) but those require more time to prepare. # Lessons learned from speaking at conferences Time has come to resurrect the blog (again)! I was never much of a blogger but 3 years is ... oh well, almost a lifetime in software industry. It's not that I don't have anything to write about (quite the opposite in fact), it's just that I have always preferred more interactive communication. So for the last 3 years I was concentrating on presenting my thoughts and experience on various conferences rather than posting them here. A huge mistake apparently which someone pointed out to me recently. On the bright side - I learned a few things about being a conference speaker and I'll share them here. If you think going down that road, here is what to expect. ### Disclaimer I'm not involved in organizing any conference nor I have any official inside information from one. I'm not even close to the famous speakers that every conference organizer dreams about, I'm not a member of any program committee, ... just a random guy how happened to have gone through various CFPs and was occasionally accepted to speak here and there. Everything I write here is based on personal observations and informal conversations with people I've met during 24 events in 13 countries (according to my [Lanyrd profile](http://lanyrd.com/profile/milendyankov/){rel=""nofollow""}) in the last 2,5 years. ### Who you are matters, no matter what they say Every conference out there desperately wants one thing *(apart from sponsors)* - high quality talks! That is of course good thing. The not so easy to answer question is "how to get those?". It should not come as a surprise to you that "known speakers are verified automatically" as someone who is involved with a famous conference admitted some time ago. Of course this does not guarantee a high quality talk but having such name in the agenda attracts people. If you are reading this, you are probably not one of those "names" and likely will not be anytime soon so quite naturally you'll have to be verified. This is where what google knows about you becomes important. The official statement is that what you have posted, what you've contributed to, what speaking experience you have, ... is what organizers are concerned about. However the bottom line from many different conversations seems to be "how many attendees your talk + you can attract?". So if you, like me, only have 300 followers on twitter and have not posted anything on you blog for almost 3 years ... well you better come up with a topic and abstract that is really really good (in the eyes of the organizers of course) and will likely make few more people willing to pay to attend the conference. ### Organizers and program committee members are people with preferences, opinions and business goals So how do you know what a really really good topic is? You don't! Most conferences will give you some hints but those are often too generic to be useful. At first it may seem that this is good as it leaves the door open for various topics and opinions. Keep in mind thought, at the end of the day, it's people that decide. Those people are often speakers themselves and have strong opinions. Those people represent companies that have particular interest in developing and promoting some technologies and making everyone forget about some other technologies. If those people are willing to pay attention to something that confront their believes and/or interests, it has to come from someone they respect (for whatever reason). Again, likely that's not you! So if you want to talk about something that is controversial or unpopular - forget about it ... or disguise it as something else. Let me give you an example. My experience shows there are significant [benefits of using OSGi](https://www.osgi.org/developer/benefits-of-using-osgi/){rel=""nofollow""} and I really want to have the opportunity to tell people about it. Unfortunately in todays Docker/Microservices biased world, almost no conference will accept such topic *(even thought OSGi was the one to introduce µServices long before it became a buzzword)*. Yet in the last 2 years I have talked about in on over 15 conferences by smuggling it in "[Microservices and Modularity](http://www.slideshare.net/MilenDyankov1/microservices-and-modularity){rel=""nofollow""}" and "[What's not new in modular Java](http://www.slideshare.net/MilenDyankov1/whats-not-new-in-modular-java){rel=""nofollow""}" talks. I almost always had full room, no one complained about talks being off topic and they triggered many interesting discussions afterwards. So conference organizers should be happy right? Uhhh no, if those were submitted as "OSGi talks", they would have never be accepted. How do I know? Ah you know ... those informal conversations over a few beers are one of the the best parts of every conference :) It probably helps if you know the right people. I've observed many times how some folks desperately try to get into some kind of relationship with famous speakers. Those guys are sitting in program committees and if you get them to remember your name it's a bonus point for your next submission. I personally never learned to play that game so can't tell you from experience. My advice would be unless you are really really smart and have something really really interesting to say - don't make a fool of yourself. But that's just me :) ### Who do you work for matters You may think working for recognized in the industry company will make it easier. From my observations, it's only true in 3 cases. The company is a sponsor/supporter/involved *(for example Oracle, RedHat, ...)* , it is a cult-like company *(for example Google)* or it's the newest and hottest startup everyone is talking about. Other that that, it not only doesn't help but can actually be an issue. I work for [Liferay](http://liferay.com){rel=""nofollow""}, a company known mostly for it's open source portal solution that successfully competes with Oracle, IBM and Microsoft as far as portals are concern. But wait, it's 2016! Who cares about portals anymore? Enterprises - yes, a lot! But developers - they are struggling to forget this technology exists while moving to containers and microservices and the coolest JS framework this week. It doesn't matter that the company has evolved and products matured over the years! It doesn't matter that we now - have state of the art modular platform and let you develop applications using almost any cutting edge Java technology from [JSF](http://www.liferayfaces.org/web/guest/showcase){rel=""nofollow""} to OSGi µServices - have on board some of the best modularity experts and JavaScript developers on Earth building things like [AlloyEditor](http://alloyeditor.com/){rel=""nofollow""} and [tracking.js](https://MilenDyankov.com/tracking.js) - are the Spec Lead for [JSR 378](https://www.jcp.org/en/jsr/detail?id=378){rel=""nofollow""} and Co-Chair [OSGi Alliance](https://www.osgi.org/about-us/){rel=""nofollow""} - have years of experience to be shared What many of the conference organizers and program committee members think when they hear Liferay is legacy technology (portlets). Not interesting for anyone. Next please! Sometimes (although not that often) I hear the argument that since Liferay is profitable company it should become a sponsor and get a speaking slot. I find this quite amusing. It seams some people think that their event is so special that companies are desperately trying to show up there for the sole purpose of self promotion. There are of course such events where it is worth doing so and Liferay has sponsored many of them. In general though, unless a company is in the business of selling something to developers, tech conferences are totally not the right place to do a marketing campaigns. For me personally I just don't go to events where someone does not appreciate the fact that my company is heavily investing in free and open source projects and is willing to cover the costs for an employee to go and share their experience and company's know-how publicly and for free. ### You'll never get to know why not Your talk proposals will be rejected many times. I suggest you to get used to it! And don't expect any explanation or feedback. The best you will get is something like *"We're sorry to inform you that your proposal was declined. Please keep in mind that we received many proposals during the Call for Papers and the available slots are very limited."*. It may have been that it was tough choice until last minute between your talk and someone else's talk. It may have been that no one actually paid attention to what you sent. It may have been that they don't trust your speaking skills. It may have been any of the other thousand things. No one ever will bother to tell you. This is the sad reality of CFPs. It's like sending a pull request that get's rejected with "Thanks, but we'll not take that from you!". It leave a bad taste but there's nothing you can do about it. I understand it would be hard to sit down and write a personalized and detailed message to the author of each submission. But I can imagine when program committee members vote on talks, they do provide their reasons. It shouldn't be that hard to have a one or two sentences of explanation and it will certainly help people improve. During those 2.5 years and tens if not hundreds of submissions, only one conference organizer *([I T.A.K.E.](http://itakeunconf.com/){rel=""nofollow""})* has contacted me expressing their concerns and asking for clarification. Everyone else was simply notifying me about their decisions. As the time passes and your experience as speaker grows it's even more weird. For example I have spoken at Devoxx BE twice *(speaking slots ware part of sponsoring package)* and as far as I can tell both talks were very well received. I even had people approaching me after the talk and asking to submit proposals to their local conferences. However Devoxx itself (BE, UK, PL) consequently rejects my talks that other conferences happily accept. It's (hopefully) not due to the lack of speaking experience. It's likely not the subject as I later on see many similar talks in the agenda. I wish I knew what was the reason so that I can learn and improve. ### Summary In general speaking at conferences is interesting and challenging experience. How much you gonna like it or hate it depends a lot of what your goal is. As someone who has learned a lot from more experienced folks, I spend a lot of time teaching others and very much enjoy when I can pass further what I have learned. But to get people to listen to you, they need to trust you. And personally I found that to be the hardest part - building credibility. When you do training on behalf of your company you only need your management to trust you. When you aim at speaking at events, you need to constantly convince random and often biased people that you know well what you are talking about and can present it in interesting way. It's hard and often frustrating but by all means worth doing. Keep in mind the above is just some thought and observations regarding aspects that I don't see anyone else writing about. There is a lot more involved into preparing a good talk and getting it accepted. There are many good posts out there to teach you how to become a speaker and how to deliver good talks. I don't feel I'm the right person to advise you what you should or should not do. If this is whole new experience for you, perhaps start by watching Matthew McCullough’s presentation on [10 Quick Tips for More Effective Conference Submissions and Presentations](http://www.youtube.com/watch?v=fJz4JJIchaY&feature=youtu.be){rel=""nofollow""}. # Java: 21 & Legally drunk! I've stated that before but allow me to repeat myself: [jPrime](http://jprime.io){rel=""nofollow""} is one of my favorite conferences. I've been there two years in a row and to quote Karol Kaliński "[it costs about 80 EUR, but can easily compete with western Europe conferences in terms of quality](http://karolkalinski.github.io/jprime-2016-summary/){rel=""nofollow""}". This year, apart from great talks, fantastic atmosphere and the cute new [Bulgarian JUG logo](https://twitter.com/bgjug){rel=""nofollow""} there was one more thing I was pleasantly surprised by, namely the conference's headline "**Java: 21 & Legally drunk!**". I have no idea how they came up with it but it surprisingly correlates with the spirit of my "[What's NOT new in modular java](https://www.youtube.com/watch?v=NKS5VU_r7Bo&index=3){rel=""nofollow""}" talk. It also perfectly describes how Java appears to me these days and perhaps can explain some of the weird perturbation and confusion that seems to be having place in the Java community these days. As Internet seems to be bloated with technical articles addressing Java's condition, I decided to have a look at it from slightly different (less serious) angle. First things first, though! When I write software, I use Java. I don't use other programing languages on a daily basis *(experiments, finding and reporting bugs in 3rd party apps and "Hello world" projects do not count)*! I'm quite happy with it regardless of all the criticism it gets and the fact it's been announced dead every once in a while for the last ... I don't know how many years! The combination of general purpose programming language and stable platform, allows me to solve business challenges in reasonable period of time. That's all that counts, as far as I'm concerned! That said, I'll happily move on to another language when and if Java becomes insufficient or inefficient tool for addressing those! Which brings me to the question that bothers me recently - "**Has the time come ...**" to which I can now add "**... or is Java simply legally drunk?**". Oh, and when I say *Java* in this post, I actually count on your intelligence to relate to particular people and organizations! If you have ever been at a party where some people *(normally nice folks)* get seriously drunk, you must know what fun it could be to see them act and behave not like who they actually are, but like who they think they are or dream of being. They'll happily reveal some of their secret, unreasonable and childish desires and believes. Moreover they try hard to convince you they can do anything, right here, right now. They'll tell you the story of their life and how it will be all different from now on. And Java makes no exception. The story goes like this: All of it's life, Java has been trying to allow companies stick their flags on the top of variety of "Business applications" mountains. Frankly speaking it's been quite successful at it. As those mountains are diverse, dangerous and generally very hard to summit, one has to carry a lot of equipment. Java has gathered such equipment over the years and helped many companies see their dreams come true. One really big backpack *(labeled "SE")* typically get's the job done! For those willing to get on even higher peaks, Java suggest to also take with you another one *(labeled "EE")*. That one is a bit different. It needs to be packed with 3rd party's equipment, compatible with Java standards! And of course clients have various backpacks of their own. But all those need to be carried and everything that's inside need to seamlessly work with everything else that's inside. This types of issues, make the journey far from pleasant *(especially when you need something that needs something else, that needs something else, ...)*. Recently some competitors discovered they can trick companies into sticking multiple flags on multiple low hills instead! And frankly speaking they have been quite successful too. Wanna climb those hills, no problem, each takes just a few things to get there. It's basically a walk, no serious climbing! The hill doesn't match your expectations anymore? Just climb the next one. Wanna talk to your coworkers? Just shout to them, they all REST on some other hills. How do you know which one is on which hill? Simple, just get your discovery employee RESTing on a well known hill! Easy peasy! And companies buy it. Of course, those who are used to Java's way and equipment try to prove they can do it too. It's just somewhat ridiculous and sometimes frustrating to have to carry all those backpacks for a "walk"! And so Java gets jealous! But there is no problem a drunk man can not solve! Think! Hmm, the problem seems to be, people don't want to walk on low hills carrying all this climbing equipment. Need to get rid of this EE backpack then! Why bother on standardizing equipment, it's so expensive and time consuming task. Equipment vendors will find a way to sell their stuff to those who need it anyway. It's their business after all. Oh see, as soon Java stated that, a number of folks announced themselves as "[Java EE guardians](https://javaee-guardians.io/){rel=""nofollow""}". With "EE" out of the way now, what about "SE" backpack? It's all useful stuff after all so ... got it ... convert every pocket of it into micro-backpack! That should work! Actually Java wanted to do that long time ago anyway. It's been what ... oh my god ... over 15 years since [JSR 277](https://jcp.org/en/jsr/detail?id=277){rel=""nofollow""} ... so it's really just about time! But it needs to do it soon, competitors are springing up quickly. No worries, Java has a drunk's man solution for that one too. It will put together, things that belong together! It will put a sticker on each micro-backpack stating which other micro-backpacks are also needed! Actually Java will go even further! It will instruct everyone else that they also need to pack their stuff in micro-backpacks with labels. If not, well Java will put all those into one huge "~~everything else~~ unnamed" micro-backpack! Simple and elegant right? But let's face it, people will be so happy not to have to carry everything, they'll go for micro-backpacks instantly! And besides everyone is using ~~docker~~ donkeys these days anyway. It doesn't matter how much you take, as long as donkeys don't get too overloaded. Here it is! Problem solved! Beware competitors, all micro-hills will belong to Java soon. And it will not give back the macro ones, hell no! If only Java was not drunk, it could perhaps listen to some folks that have been on those Java trips from the begging, experiencing and solving *(or figuring out a ways around)* those issues. It could join forces with them and learn from the mistakes they made in the past and the solutions they have now. It could realize that while micro-backpacks are indeed essential, they are by far not enough. It could try to look in future beyond the buzzword horizon and imagine that while donkeys and labels makes things easier to carry and find, having a drone that will deliver the right equipment at the right place exactly when needed, is what many companies would much prefer despite the investment. In long term, it could be far more beneficial for Java to try to address the shortcomings of current low hills offerings by making the mountain climbing experience as much fun as it is a challenge! Let's hope Java will realize that, once it gets sober! # Micro-services or μServices Yesterday someone very well known and respected in Java world *(I didn't ask him for permission, so I'm not mentioning his name)*, approached Liferay's booth at JavaOne. He expressed his concerns about the word "**μServices**" in the message printed on our booth's wall. I wasn't there at the time this happened. I spoke with my colleagues few minutes later, as the non-developers were getting worried we made a terrible and embarrassing typo. As a non-native English speaker I wasn't quite sure what the exact argument was, but it was clear to me the person believed we should have used "**micro-services**" instead. I urged to reassure my colleagues this is not a typo but an important differentiator in today's buzzword driven world. ![Liferay booth at JavaOne 2016](https://MilenDyankov.com/assets/2016-09-21-microservices_or_mServices/liferaybooth.jpg) That is something that happens often in fact. Everyone is crazy about **micro-services** these days and what they usually mean by that is something that should probably have been called "[cohesive web-services](http://blog.osgi.org/2014/06/software-mixed-with-marketing-micro.html){rel=""nofollow""}". Whoever coined the **micro-services** term couple of years ago, clearly didn't known *(or didn't care)* that [the concept of **µServices** was introduced back in 2010](http://blog.osgi.org/2010/03/services.html){rel=""nofollow""}. It basically refers to the idea of using "OSGi service as a design primitive" where the term "micro" actually make sense. So now we have two terms that are pronounced the same way but mean totally different things implementation wise! While **micro-services** is clearly a misuse for what it describes, I would be the most naive person in the word if was to believe a that a buzzword carefully implanted in so many minds by companies structuring their entire business around that buzzword, is something that can be changed. Luckily we can at least write them differently. So, be my guest, and keep writing "**micro-services**" to describe cohesive web-services and keep fooling yourself they are "micro" just because what you compare them to is gigantic. But please be generous enough to let us, the OSGi and clean architecture fans, use "**μServices**" for the good old tiny, independent, cohesive Java services that allow us to achieve the same goals without the overhead of web-services. And as for the spoken form, I typically use "**in-VM μServices**" just so I can distinguish from the buzzword when I talk to biased people. It's sad that even respectful and smart people don't know the facts. Luckily I could find the person who made the comment and chat with him for 5 minutes. That's all it took to get that person to understand what we meant by "**μServices**" and to admit he wasn't aware of the facts. He even expressed some interest in understanding better what OSGi has to offer these days. Of course that will not turn him into OSGi fan or advocate, but that is totally not the point. The point is to understand there are many technologies and methodologies and they all have strong and weak sides. The point is to know and not blindly trust buzzwords. The point is to respect each other regardless of what tech choices we've made. # Developers about OSGi Some time ago I published a survey asking developers what they think about OSGi. It took a while to reach some reasonable amount of responses and then to process the results, but finally I'm ready to publish them. In a few weeks period there were 220 responses to the survey. Even though there was no question about the location, I'm pretty sure they come mostly from Europe. That is because I could see the responses coming in groups as the survey was promoted at particular local JUGs. Another thing to take into account is that the information about it reached way more than 10000 developers (based on the published number of members of the groups the information was published). With that in mind you can hardly consider 220 responses representative. Never the less it gives you some ideas and things to think about. #### What is your favorite JVM programming language? The question was put this way on purpose. I was interested in what JVM language people actually like and not what they use because of company rules and policies. What language people like tells me more about from what perspective their judgements are made. Surprisingly though, it seams Java is still strong: :iframe{frameBorder="0" height="371" scrolling="no" seamless="true" src="https://docs.google.com/spreadsheets/d/12lDBu_BM5DnStMOcKNKZ_J6jc6xaTIYzM2_DCZoKrxM/pubchart?oid=1184654248&format=interactive" width="600"} #### Years of experience in building software? The purpose of the question was to make some analysis of how professional experience reflect opinions. You may be surprised to note that either our industry is getting old or young people don't care much about surveys: :iframe{frameBorder="0" height="371" scrolling="no" seamless="true" src="https://docs.google.com/spreadsheets/d/12lDBu_BM5DnStMOcKNKZ_J6jc6xaTIYzM2_DCZoKrxM/pubchart?oid=886915795&format=interactive" width="600"} #### What best describes the source of your knowledge about OSGi? Before asking people about their opinion I think it's important to understand what how much of that it is backed by experience or knowledge. But then again, it's very likely that only people with strong opinions participated so don't be too quick to judge. This could also explain the low number of people not familiar with OSGi: :iframe{frameBorder="0" height="371" scrolling="no" seamless="true" src="https://docs.google.com/spreadsheets/d/12lDBu_BM5DnStMOcKNKZ_J6jc6xaTIYzM2_DCZoKrxM/pubchart?oid=621161433&format=interactive" width="600"} The answers under "other" were: - Wrote my graduation thesis about it (static quality analysis tool) - Apache Karaf helper - I'm an OSGi EG chair - I know OSGi, but don't USS it - In the past I used and developed OSGI extensivly - I have given training on OSGi - I've experimented with OSGi and plan to use it in a team for a professional project #### What would you primarily use OSGi for? Time for some opinions (move your mouse over the answer to see full text). As people could give up to 4 answers, the numbers can hardly be presented as percents. I personally find them quite interesting though: :iframe{frameBorder="0" height="371" scrolling="no" seamless="true" src="https://docs.google.com/spreadsheets/d/12lDBu_BM5DnStMOcKNKZ_J6jc6xaTIYzM2_DCZoKrxM/pubchart?oid=1980600680&format=interactive" width="812.5"} #### Think of a project where OSGi could add value but wasn't/wouldn't be used! What was/would be used instead of OSGi? And finally alternatives. Over the years I learned that asking people "what you consider to be an alternative for X" gives you quite good idea about what they think about X. So here it is, and to my surprise "micro-services" is not the leader: :iframe{frameBorder="0" height="371" scrolling="no" seamless="true" src="https://docs.google.com/spreadsheets/d/12lDBu_BM5DnStMOcKNKZ_J6jc6xaTIYzM2_DCZoKrxM/pubchart?oid=412865583&format=interactive" width="600"} The answers under "other" were: - not sure I understand the question - Custom module system - none of the above provides for the same as OSGi (even microservices) - "value added not worth the effort", primarily because the nice classloader scheme cannot be used outside of OSGi. Too tightly coupled to lot of cruft. - Basically everywhere where you know it is going to be large, complex, and keep changing with time - PF4J (2 answers) - I could mark most of answers. - Plain Java ClassLoaders - Server farm solves 'hot' deployment of new jar versions - I have no idea (2 answers) - heh - NetBeans platform - custom classloaders #### Summary As I said in the beginning, I don't fool myself this survey is representative. There seams to be far more Java developers out there not aware of what modularity is and why it matters. So the only conclusion I can make for sure is that there is a lot of room to popularize OSGi among Java developers. If you want to learn more details, feel invited to attend my "OSGi for outsiders" talk at [OSGi Community Event 2016](https://www.eclipsecon.org/europe2016/session/osgi-outsiders){rel=""nofollow""} and [ApacheCon Europe 2016](https://apacheconeu2016.sched.org/event/8ULH?iframe=no){rel=""nofollow""}. If you want to play with the data yourself, [here it is in CSV format](https://MilenDyankov.com/assets/2016-09-30-developers_about_OSGi/OSGi_survey_results.csv)! # All Those Little Things As developer advocate, I do a fair amount of traveling. Most of my journeys start with about an hour long drive to the airport. It's nice highway, not a big deal, easy to do it without needless stops. Yet I like to make one stop, get myself out of the car for a while, grab a coffee, smoke, ... One of the first times I drove there, I stopped at some gas station. It was my first time there. As I was getting out of the car, I realized they had a special place for pets, nearby the main entrance. There was bowl of water and another empty one next to it (presumably to be used to put some food in it). There was also a sign, encouraging pet owners to ask for any assistance they may need. I found this interesting, and proceed to the restroom. A somewhat strange hanger on the wall attracted my attention. It turned out to be a dedicated hanger for motorbike helmets. I drive a car and I never travel with pets. So those things should be irrelevant to me. Yet, for the last 3 years, that is the only gas station I ever stop at on that highway. Exactly for those two reasons. Why? Because what they did, demonstrates an attitude. They are not making any extra profit from dog owners despite the fact they likely have more cleaning to to. And bikers typically will purchase less fuel than car owners. Yet they have tried to make the travel experience of those people a little bit better. At no extra cost! Call it clever marketing if you wish. As far as I'm concerned though, someone thought of a way to make certain travelers feel better! In a world where many others would have been thinking only how to take advantage and extra charge those same people, that is something I appreciate. All those little things that indicate you are first and foremost a human being only after that an entrepreneur! One of the struggles we at Liferay's developer relations team sometimes have, is to explain to some heavily sales oriented people, what is the purpose of developer relations. I think our role is exactly this - make sure Liferay still provides all those little things. Make developers' journey a bit better (or perhaps much better) at no extra cost! Make sure the open source and community spirit comes first and only after that the enterprise services. It's not an easy task, but well worth doing it. And if you can, you should probably try that in your company. And then perhaps one day we all will live in much more friendly world! # Getting feedback live I have spoken at [quite some conferences](https://MilenDyankov.com/talks) over the last years. Part of the talks were just me speaking with some (hopefully not too ugly) slides behind me. Some were live demos. Either way, I'm almost never happy with my talks and therefore constantly looking for ways to improve. But in order to improve, first you need to know what your audience like and don't like. It all comes down to feedback and constructive criticism. Some conferences are quite good at collecting feedback. Polish [Confitura](http://confitura.pl){rel=""nofollow""} is on the top of my list, sending me a document that not only shows how people voted but also all the comments from their online survey. Most conferences though don't bother to give feedback to speakers. Some don't ever bother to collect it. It's therefore been on my mind for a while to try to find a fun and easy way for attendees to provide feedback during *(not after)* my talk. ### About feedback Based on many conversations I've had with attendees in the past, I figured they are more likely to give feedback the moment they disagree with you or the moment they are impressed by something. If you ask them after the talk, the moment is gone and all you get is the overall feeling, most of the time highly influenced by what you have said in the last 5 minutes. Let me give you an example. I have a couple of 45min to 1h long talks about Java modularity. Apart from introducing myself and who I work for in the beginning, I only talk about technologies, methodologies and concepts and never mention [Liferay](http://liferay.com){rel=""nofollow""} for about 95% of the time I have. Than I take 3 to 5 minutes at the end to explain how we apply those concepts in our products. This looks like both fair and useful approach to me. Yet every time I get feedback **after** the talk, there will be some people complaining that I'm advertising my company too much. And that would be actually OK if it wasn't the only thing they have to say about the whole talk. So I thought, what if I can get them to send feedback **during** the talk via some web application. Most people are constantly online these days anyway, so it shouldn't be too big issue. To make it more fun and motivate more people, I could even display their comments on the big screen once in a while during the talk. I can then adjust my talk based on their comments and ratings and answer their questions while still relevant without them having to interrupt me and shout from the other end of the room. I had no idea if this will work, but it was worth trying. The only question was, how do I build this web application? ### There's an app for that Building simple app as "Talkback" *(this is what I ended up calling it)* was amazingly easy and straightforward! OK, that's a bold lie! It wasn't! I struggled a lot with the UI part. Not only I'm not any good UI developer but *(since I'm modularity freak)* I also decided to learn better [Polymer](https://www.polymer-project.org/){rel=""nofollow""} and [WebComponents](https://en.wikipedia.org/wiki/Web_Components){rel=""nofollow""} while doing it. So building a static HTML site out of web components took me quite some time and raised my frustration to higher level! But at the same time I learned a lot about front-end modularity *(which is perhaps a topic for another post)*. At the end it wasn't the prettiest thing on earth, but for someone who has spend his entire professional life on the backend side, I was satisfied with the result. ![Talkback live coding at JavaSkop](https://MilenDyankov.com/assets/2017-03-16-Getting_feedback_live/javaskop_talk.jpg) With a static HTML app in place, I had to implement authentication and some remote services to store and retrieve the data. OK, "implement" is what I would have to do without **[WeDeploy](http://wedeploy.com/){rel=""nofollow""} - "the service that gives you access to intuitive APIs and help you create modern apps faster"**. Yes, it is built by [Liferay](http://liferay.com){rel=""nofollow""}. Yes, you can again blame me for advertising the company I work for. But it doesn't change the fact it's a great service that allows rapid application development! And no, you don't need to take my word for it! You can *(and if fact should)* try it yourself! It's free! So, in the case of "Talkback" all I had to do was to configure `auth` and `data` services provided by [WeDeploy](http://wedeploy.com/){rel=""nofollow""} and I was done! Learning curve aside and having the UI ready, one can really build such app in minutes! I thought that was something worth sharing and this is how "From 0 to production in one conference talk time" live demo was born. ### Feedback about the feedback app The first conference to give me the opportunity to present it *(or run my experiment if you prefer)* was [JavaSkop](http://jug.mk/javaskop17){rel=""nofollow""} - a lovely event in Skopje, Macedonia. And it went surprisingly well! People seamed to have fun playing with the app as I was creating and deploying it! It was a relief to see so many *(now I know the number is 25)* "thumb up" comments, 3 "average" ratings and only two "thumb down"s. There were a few questions as well, some of which I left unanswered, so let me fix that here: - **Heroku ?** - A few questions, though asked differently, were basically about the difference between [WeDeploy](http://wedeploy.com/){rel=""nofollow""} and [Heroku](https://www.heroku.com/){rel=""nofollow""}. As I don't know exactly what Heroku offers let me quote my colleague [Zeno Rocha](https://zenorocha.com/){rel=""nofollow""} here *"Heroku lets you deploy any kind of application there, this is something WeDeploy does as well but Heroku doesn't offer you APIs that you can easily plug into your app like WeDeploy Auth, Data, Email, etc. Hosting is very important but it's just one part of WeDeploy, we believe there's a lot of value in those microservices"* - **OpenShift ?** - Same as above, there is conceptual difference between [WeDeploy](http://wedeploy.com/){rel=""nofollow""} and [OpenShift](https://www.openshift.com/){rel=""nofollow""}. OpenShift focuses on making it easy to manage Docker containers. In another words, you *(or someone in your team)* still needs to manage the infrastructure, just in a more modern way. And so one can of course setup and run own Auth, Data, Email, ... services running on top of OpenShift *(or Heroku or any other PaaS)* for the cost of maintaining those. WeDeploy's goal on the other hand is to serve best application developers and make infrastructure for them as transparent as possible so they can focus on building their awesome apps. - **do the WeDeploy have limit of pinging other servers?** - Not that I'm aware of. Keep in mind though, at the time of writing this post, WeDeploy is in alpha *(oops I guess I said "beta" during the conference)*, so things may change. - **can we deploy vanila java ee app?** - As I demonstrated during the last few minutes of the talk you can [run Java applications](http://wedeploy.com/docs/other/java.html){rel=""nofollow""} *(SpringBoot based one in this case)*. I've also run OGSi based applications on it. I'm pretty sure you can run Java EE applications on it if you manage to package them as single executable jar *(for example by using one of the solutions from [MicroProfile](https://microprofile.io/){rel=""nofollow""})*. It can also run [Liferay as a service](http://wedeploy.com/docs/other/liferay.html){rel=""nofollow""}, which is quite complex Java EE app :) If you have more questions about WeDeploy please use the conversations on [wedeploy.com](http://wedeploy.com/){rel=""nofollow""} or join [WeDeploy's Slack](http://wedeploy.slack.com){rel=""nofollow""}! ### Back to feedback So that's how the experiment went! I got quite some feedback about the technologies I demonstrated. I also got zero feedback about how I was doing as a speaker :) I guess I need to figure out a way to encourage people to rate that aspect too. Overall, despite the fact I'm still scared I'll see many "thumbs down", I'm looking forward to improve the app and "build it from scratch" at few more conferences in the future. What do you think about the idea? Would you use something like "Talkback" to get feedback live during your talk? Honestly speaking, it's not something I was planning to release as service that can be used by others, but with WeDeploy that would be quite easy. Or do you have better ways to collect feedback? I'd love to hear your stories. # What to expect in post-JPMS Java world The atmosphere around Java 9 (and most notably JPMS a.k.a. JSR 376 a.k.a. Jigsaw) is getting really hot. Java community seams to be divided into 3 camps "developers who honestly believe JPMS can simplify modularity", "developers who have been dealing with modularity long enough to clearly see the issues Java platform architects don't want to see" and "developers who don't care (for now)". I personally think the 3rd group is by far the largest and this is the main issue and the main reason for the noise. Why? Because those are the developers who never cared about modularity. Most of them still don't care, but now they will be forced to learn about modularity. The question is what will they learn? Real modularity as described in [Modulariy Maturiy Model](http://enroute.osgi.org/appnotes/modularity-maturity-model.html){rel=""nofollow""} or limited version of it wrapped in a package with a label "simple" on it? This is not a new thing! The battle between "good quality code" and "simple to write code that works" is something that takes place in every project! And you know which one wins most of the time. At least I think I've been in this industry long enough to know, so I though I'll write down a few prediction based on what I think will happen to JPMS and what impact it will have on Java projects in the following months. ### Java 9 release will not be delayed again I'm almost ready to bet on that! And no, it's not because I believe JPMS is good and ready to be released. Quite the opposite in fact. I think there are still many issues that needs to be addressed. But I've been observing the work on JPMS long enough to know no issue can stop Oracle from moving forward! Let me give you an example - here is a short summary of how Public Review was published: - Mar 7 : [Mark Reinhold asks for feedback on the draft he intends to post for Public Review](http://mail.openjdk.java.net/pipermail/jpms-spec-observers/2017-March/000799.html){rel=""nofollow""} - Mar 8 : David M. Lloyd: '[Right now a Public Review definitely does not seem appropriate ...](http://mail.openjdk.java.net/pipermail/jpms-spec-observers/2017-March/000804.html){rel=""nofollow""}' - Mar 8 : Robert Scholte: '[I agree with David. There are still quite some topics marked as Proposal posted or discussion active" ...](http://mail.openjdk.java.net/pipermail/jpms-spec-observers/2017-March/000809.html){rel=""nofollow""}' - Mar 9 : Neil Bartlett: '[I also agree with David. There is not sufficient consensus ...](http://mail.openjdk.java.net/pipermail/jpms-spec-observers/2017-March/000811.html){rel=""nofollow""}' - Mar 12 : Tim Ellison: '[... I think it is somewhat premature to call for a review until the EG has let the current discussion run its course ...](http://mail.openjdk.java.net/pipermail/jpms-spec-observers/2017-March/000821.html){rel=""nofollow""}' - Mar 15 : Mark Reinhold: '[We do not have consensus in this EG on moving forward to Public Review ... It is, however, in the best interest of the wider Java ecosystem to proceed ... I have therefore submitted the specification for Public Review](http://mail.openjdk.java.net/pipermail/jpms-spec-observers/2017-March/000828.html){rel=""nofollow""}' So, if you are familiar with [Scott Stark's famous blog post](https://developer.jboss.org/blogs/scott.stark/2017/04/14/critical-deficiencies-in-jigsawjsr-376-java-platform-module-system-ec-member-concerns?_sscc=t){rel=""nofollow""} and/or you have heard that both [RedHat and IBM announced they will vote "No" on JPMS](http://mail.openjdk.java.net/pipermail/jpms-spec-experts/2017-April/000684.html){rel=""nofollow""}, you may be thinking that it is in "**best interest of the wider Java ecosystem**" to spend some more time on Java 9 and deliver a better quality product. You are wrong! This is not the first time EG member points out that [inadequately addressed issues obligates them to vote "no"](http://mail.openjdk.java.net/pipermail/jpms-spec-observers/2017-February/000763.html){rel=""nofollow""}. What do you think the reaction was? I'll simply quote [Mark Reinhold's response](http://mail.openjdk.java.net/pipermail/jpms-spec-observers/2017-February/000775.html){rel=""nofollow""} and let you decide for yourself where it fits in your own arrogance scale: > "You can choose to vote "no" anyway, of course, if you decide that it is more important to protect your own narrow interests than it is to support the **broader interests of the entire Java ecosystem**." Wow! Oracle is guarding the interests of the entire Java ecosystem while RedHat is only protecting their own narrow interests! This is the first time I witness a company mostly famous for its high price tags and very successful legal department to claim it represents the community better then widely recognized Open Source Software vendor! It comes as a surprise to me but who am I to argue with the Chief Architect. What about IBM? Well, Mark Reinhold claims in his "[An Open Letter to the JCP Executive Committee](http://mreinhold.org/blog/to-the-jcp-ec){rel=""nofollow""}" that *"IBM has decided that their interests are best served by delaying this JSR"* ! As you can see, here again he reaches for the universal argument "whoever disagree with us, is trying to sabotage us"! So yes, I'm pretty sure Java 9 will be released as planned regardless of how many EG members vote "No"! Then Oracle's marketing machine will start to position it as the best modular system on Earth. And if someone dares to say something else ... well here is the next prediction: ### People raising concerns about JPMS will be "by definition, stupid and ugly" I don't actually picture anyone repeating the [famous Linus Torvalds quote](https://www.youtube.com/watch?v=n-IgqiTD4XA){rel=""nofollow""} in some serious discussion, but I can sense this kind of attitude among Java platform architects and some enthusiastic JPMS supporters! And of course "stupid" and "ugly" are metaphors! What we will actually see is those being well masqueraded: - Hidden message: **If you use those you are stupid!** - Making more fun of OSGi *(those of you who have been at JavaOne 2016 know what I mean)* and now JBoss Modules. - Constantly repeating how complex other modular systems are - Pointing out products who tried other modular solutions and failed - Hidden message: **They are the competition and they are ugly!** - more and more companies being accused of sabotage or protecting own interests - more and more modularity experts being called biased - more fake claims that there are no successful modular systems so their authors are jealous I can't predict the scale, but I have no doubt those will be the main arguments. You can clearly see it in Mark Reinhold's comments mentioned above as well as in Mike Hearn's "[Is Jigsaw good or is it wack?](https://blog.plan99.net/is-jigsaw-good-or-is-it-wack-ec634d36dd6f){rel=""nofollow""}" blog post. It is sad to see this happening! Especially taking into account that every other modular system must run on top of JPMS and play by its rules! In another words, no modular system can hurt JPMS in any way! However JPMS can make the life of the developers of real modular systems much much harder. In fact, reading JPMS mailing list I sometimes have the feeling this is an undocumented goal of JPMS. ### Most 3rd party module names will have version as part of the name As much as Java platform architects try to fight with it, I believe most 3rd party JPMS modules will contain a version in their name. Not only JPMS does not give any good way to version modules, it also tries to discourage usage of version numbers in the module names. Therefore if your module name ends with digit you'll see a warning message! Do you recall what people do when they have to deal with a system that enforces ridiculous rules? Yep, exactly that, they game the system! So I can picture modules ending with "\_" or "v" or something else after some meaningful version number! Why would developers need to do that, you may ask? Because, unlike classpath, module path can not have 2 modules having the same packages! JPMS will not try to make any decisions in such case, but simply fail. So ultimately it's the developer that needs to know which module need to be on the module path! But when a developer has an app that depends on module `A` which depends on module `B` and there are several modules named `B`, which one the developer should choose? Even if all those modules were assembled providing some version information *(lets leave aside the awkwardness of the [proposed version strings](http://download.java.net/java/jigsaw/docs/api/java/lang/module/ModuleDescriptor.Version.html){rel=""nofollow""})*, it is still hidden from the developer inside a compiled `module-info.class`. So what is the most natural thing that developers inexperienced in modularity and forced to deal with modular system of limited functionality will do? They'll "solve" the problem themselves by calling modules `B_1_0_`, `B_1_1_0_`, ... and so then specific version of `A` can depend on specific version of `B`! Easy! Wrong but easy and works! Yet even this will not convince the people behind JPMS, they have made the wrong decision. Why? Because this works perfectly well for JDK itself. All the JDK modules are written by the same company and only released together *(in few flavors)* as JDK. Therefore versioning individual modules makes no sense as all conflicts are easy solvable. The strange belief that any software can be delivered this way, seems to be way too strong with Java platform architects. ### IDEs will generate open modules by default If you have read [Effective java](https://books.google.com/books/about/Effective_Java.html?id=ka2VUBqHiWkC&redir_esc=y){rel=""nofollow""} you may recall that **"The rule of thumb is simple: make each class or member as inaccessible as possible."**! There are number of articles, blog post, Q\&As, ... that tells you to keep your classes "package private" unless you really need them to be public! Yet every single IDE I know, generates public class by default! Have you ever wandered why? Because working with "package private" classes is a pain. And for most developers, encapsulation is not good enough reason to deal with that pain. I'm pretty sure the same thing will happen with modules. Most *(if not all)* IDEs will either generate them as open by default or will have an option to do so that most developers will turn on. If you don't know how open *(a.k.a. weak)* module differs from a normal *(a.k.a. strong)* one, you can [read the proposal](http://mail.openjdk.java.net/pipermail/jpms-spec-experts/2016-October/000430.html){rel=""nofollow""}! Long story short, it's a workaround to fix broken reflection in JPMS. Long after the core concepts of JPMS were designed and developed it became clear that the decision to restrict access instead of visibility impacts every framework that uses reflection. Open *(originally called weak)* packages are such that are not exported but can be accesses via reflection at runtime. Open module simple indicates all packages in that module are open. Given the amount of popular frameworks using reflection, I can imagine most developers will choose to simply open their modules. Especially considering that there is no real penalty for doing so! ### Tools to "export everything" will become popular While many projects claim they don't care too much about JPMS since they can stay on classpath, that may not be very easy thing to do long term. I'm pretty sure there will be functionalities that will be available only as modules. There will be companies that would be forced one way or another to use modules. In general I'm pretty sure Oracle will make sure developers can't ignore JPMS as easy as they can ignore OSGi. And here again developers will have to game the system to stay productive. The first [WeakeningAgent](https://gist.github.com/raphw/91c81b8afdfd76ccfd87508a0af0e8bb){rel=""nofollow""} was released several months ago. I suspect many more of those will show up shortly after Java 9 is released. If the tool needs to crack open, change, recompile and reassemble files, it will do so. There are many smart people out there working on really important projects that don't have any time to waste on pleasing some constrained modular system. I'll personally be very sad to see this happening but I wouldn't blame anyone for trying to be productive. This could have been avoided if JPMS was to only modularize the JDK and give 3rd party developers the freedom to use modular system of choice for anything above that. Unfortunately "what works for JDK must work for you" attitude was adopted and JPMS was positioned as general purpose modular system despite the fact that fundamental features in any such system are clearly stated to be "non goals" for JPMS. Therefore many people believe JPMS stands on their way by adding restrictions while not solving any real problem. Contrast that with OSGi. It helps solve specific problems is certain types of applications and many developers appreciate its power and flexibility. Yet any Java developer not facing those problems does not even need to know OSGi exists. Much in the same way, every Java developer not getting any benefits from JPMS will have to make it "disappear". And there will be an app for that! ### Real modular systems will support JPMS modules While there is an attempt to achieve some kind of interoperability between different modular systems and JPMS, I don't think this would result in anything useful. IMHO neither Oracle nor Java platform architects have any interest in that *(apart from getting an "yes" vote)*. Of course the topic has been discussed and everyone seems to agree interoperability is beneficial for the Java community. The issue here seams to be how is interoperability defined. For Oracle that is basically "adapt your non-standard platform to use JPMS internally". I don't see that happening. JPMS is way too limited and we are yet to discover how the layer concept will perform in large dynamic systems. On the other hand, there is not a single thing JPMS can do, that other modular systems can not. So converting a JPMS module to some alternative format should be relatively easy using some heuristics. For example there are tools that can generate OSGi bundles out of plain JAR files. Given that JPMS modules contain even more meta information than plain JAR file, it should be even less problematic to convert those. And for developers who are already using modular systems, that would likely be the best option. That is to run their modular platform of choice on top of JPMS with minimal set of modules required by the platform. Then install/configure/update/unistall both platform specific as well as JPMS modules as needed. I'm not aware of anyone working on something like that, but I believe chances are we'll see something like that already in 2017. ### Summary All of the above is my personal oppinion based on ... you don't want to know how many ... years of experience in software development. The pattern was always the same: someone had a great idea of making a complex domain look simple. Remember WebStart? CMP? CORBA? JNI? ... And I bet every Chief Architect at some point said something like "I've seen many projects fail due to bad decisions, I'll not let this happen to this project". Life however is perfect tester, history likes to repeat itself and meanwhile developers have things to do. Happy coding everyone, with or without JPMS :) # Cleaning up my GitHub mess You know how it goes. You continuously stack stuff in the most convenient place (shelf, drawer, desk, ...) and it's all fine, up until the moment you no longer can find what you need. That is the day when you need to put everything else aside and clean up your mess. Not sure if it is Conway's Law to blame but this seams to happen to my repositories on GitHib. And today was the day when I no longer could recall which repo is under which account, where it resides on my local hard drive and if it's actually in sync. So today was my GitHub cleanup day. Just in case you need to cleanup yours or if you use one of my projects and something is no longer where you expect it to be, here is what changed. ### The root cause of the mess [Azzazzel](https://github.com/azzazzel){rel=""nofollow""} is my private account which I have had since GitHub's early days. As of today it had over 30 repositories. If that was not enough, this user is member of [Commsen organization](https://github.com/Commsen){rel=""nofollow""} which represents the company I own *(that's why my open source projects use `com.commsen` as package prefix and Maven group id)*. That organization account had another 5 repos. [MilenDyankov](https://github.com/milendyankov){rel=""nofollow""} is the GitHub account I created after I joined [Liferay](http://liferay.com){rel=""nofollow""}. This user is member of [Liferay organization](https://github.com/liferay/){rel=""nofollow""} *(plus few other Liferay related ones)* and this is where all Liferay related work goes. Unfortunately I've also occasionally used it to throw there demos, PoCs, small projects, ... As of today it had another 15 repositories. Altogether there are 50+ repositories which contain: - clones of company projects - open source projects I'm working on or maintaining - open source projects I've released ages ago which are no longer maintained - clones of open source projects I've contributed to *(often just once)* some time ago - demo code from my [talks](http://milendyankov.com/talks){rel=""nofollow""} - sample code to demonstrate a functionality or reproduce an issue I'm sure for many of you those numbers are far from impressive, but for me this was the point where I started to get lost and I had to do something about it. ### Using more than one GitHub account I've been using two github accounts for several years now. I didn't have to change anything to continue doing it. I just though I'll share with you my approach in case you have the same issue. So what I do is as simple as configuring my `~/.ssh/config` file like this: ```text Host milendyankov.github.com User git Hostname github.com IdentityFile ~/.ssh/ Host azzazzel.github.com user git Hostname github.com IdentityFile ~/.ssh/ ``` Then I make sure my git remotes use appropriate hostname instead of just `github.com`! For example: ```text → git remote -v origin git@azzazzel.github.com:azzazzel/modular-dukes-forest.git (fetch) origin git@azzazzel.github.com:azzazzel/modular-dukes-forest.git (push) ``` This onetime configuration allows me to tie a particular local clone to appropriate GitHub account. Of course if you know of a better way, please let me know. ### Get rid of the clones Back to clean up task. The most obvious thing is to get rid of what you don't need. It doesn't make sense to keep clones of someone's repo just because you've contributed to it in the past. I have no idea why I kept those so long but now they are gone. I can always clone them again if I need to. ### Decide what goes where Now the hard part - the repositories that are (or could be) actually in use. After trying out different things I decided to group those into 4 categories: - **anything that contributes to artifacts managed by Liferay** goes under [MilenDyankov](https://github.com/milendyankov){rel=""nofollow""} account. Fortunately all *(currently 7)* of those were already there so nothing to do about it. - **my demos, examples, PoCs, clones of projects in use, ...** go under [Azzazzel](https://github.com/azzazzel){rel=""nofollow""} account *(currently 20)*. I had to transfer ownership of few projects but GitHub makes the process really simple. It also automatically redirects to the new locations if you use the old URLs. - **my Open Source projects** go under [Commsen](https://github.com/Commsen){rel=""nofollow""} account *(currently 6)*. Here again I had to transfer ownership of quite a few projects! - **my old, no longer maintained projects** go under [Commsen Archive](https://github.com/CommsenArchive){rel=""nofollow""} account *(currently 11)*. Since GitHub does not provide an easy way to mark and filter out old projects, I had to improvise. I created a new organization account for archived projects and moved all old projects there. And that's it ... well, almost! ### Redirect project sites to new locations While GitHub does great job redirecting all links to the Git repository on the Web and through Git activity, it doesn't redirect GitHub Pages associated with the repositories. Luckily quick search found me [Redirecting GitHub Pages after a repository move](https://gist.github.com/domenic/1f286d415559b56d725bee51a62c24a7){rel=""nofollow""} which was all I needed. In my case project sites move from [Azzazzel](https://github.com/azzazzel){rel=""nofollow""} to [Commsen](https://github.com/Commsen){rel=""nofollow""} account. Since both already host websites *(milendyankov.com and commsen.com respectively)*, it was just a matter of creating the appropriate `[project-name]/index.html` files in the former with the following content: ```text Redirecting to http://commsen.com/[project-name]/ ``` Now you should be automatically redirected to the new place if you go to [WeDeploy Java client](http://milendyankov.com/wedeploy-client/){rel=""nofollow""} or [WeDeploy Maven Plugin](http://milendyankov.com/wedeploy-maven-plugin/){rel=""nofollow""} for example! ### Final thoughts I'm about to clean up my local filesystem now, but I will not bother you with the details. Enough to say, now that I've decided in which space given codebase lives, it's kind of obvious how to restructure it. I tested the changes as much as I could and everything seams to work fine *(including existing clones of the moved repositories)*. However if you encounter missing or broken links or any other issues, please let me know. Of course, if you have a better strategy for keeping you GitHub accounts and repos nice and tidy, please share! # Am I against JPMS and microservices? I started writing this on my way back home from Devoxx BE 2017. The reason is, two things happened during the conference, that made me ask myself this question. First one was a conversation with a very well known person working on Java who was curious to know what benefits I *(or perhaps the company I work for)* get from publicly speaking against JPMS *(a.k.a. JSR376 a.k.a. Jigsaw)*. The second was a message from a colleague of mine who was asking me to explain how do I feel about microservices because he apparently saw somewhere I'm publicly speaking against them. That got me thinking. Is it possible that two of my most popular talks, namely "What's not new in modular Java?" and "Microservices and modularity or the difference between treatment and cure", were picturing me as the person who is against JPMS *(and therefore Java 9)* and against microservices. ### I must admit ... ... that is very likely. Especially to those people who only saw the slides but didn't see the talks ([recordings of which are available on YouTube by the way](https://www.youtube.com/playlist?list=PL5nbto3Wgyn1Z89SN8dXqqSIZdsqjlII5){rel=""nofollow""}). I don't do bullet points. I only use slides to make it easier for my listeners to memorize what I say by attaching to it some visual representation. That approach has a major drawback - looking at the slides alone one can interpret them in may different ways. That is unfortunate but it is unlikely that I'll change my presenting style any time soon. If I do, the change would be to rather not use slides at all. But I agree those slides in question, deserve an explanation and here it comes. ### So, what I have against JPMS and microservices? NOTHING! I am not against JPMS/JSR376/Jigsaw/Java9! I am not against microservices! I also have NOTHING against SpringBoot or Oracle or Netflix or Pivotal or any other technology or company I've mentioned in my talks. That's it. Now you all know! ### But, there is always a "but" There are things that are very hard for me to accept. I find it hard to accept bending the truth. I can't accept that being right or wrong about purely technical aspect is a function of how much money your company is invested in given technology. I feel the need to react when I see arrogance and disrespect for people who have spend most of their lives contributing to the IT industry and Java ecosystem in particular. I'm against making young developers believe there is no need to use common sense or logic or reason about things, because there is already a solution from a famous company or person. ### Why do I care? You may as well ask a parent why they say to a child *"do not touch that!"*. Is it because they are against the oven or the power socket? I have spend last two decades either writing or helping other people write enterprise software. I believe in OpenSource, giving back and sharing knowledge. I believe teaching young generation of developers fundamental software concepts is more important than promoting a product. I learned that the hard way. I have "survived" more than one "buzzword tsunami". I have seen a lot of poorly designed software systems that seemed great idea at first. I've lost track of the number of terrible architectural decisions I have made myself. So today, when people claim they have designed an easy and universal solution for a complex problem, I'm very suspicious and I question it. Especially if the people making the claim, are in position to shape the minds of generations of developers. ### About JPMS The Java Platform Architects are very smart people who are very well aware of the limitations and design issues JMPS has. They are also well aware of how vague terms such as modularity, strong encapsulation, reliable configuration, ... are. I wish they were both honest and brave enough to say something like *"We understand modular software means different things for different use-cases. This is what we've designed because it made most sense for JDK. Our design only covers some part of the whole concept. Those are the parts we'll cover in future Java releases. Those are the parts we do not plan to cover because of such and such reasons. We understand your use-case can be different and we'll do our best to make it easy for you to use those other solutions to solve the challenges you face."* ### About microservices It is not much different. Consider the Netfix, Google, Amazon, ... examples showing you how thousands of microservices are easily orchestrated in the cloud and happily self-recover after a monkey has introduced a significant chaos. Those messages come from very smart people who are very well aware of the fact that what they show you is a particular approach for achieving application decomposition *(a.k.a modularity)* that itself comes with some overhead and well hidden surprises. Yet microservices are presented with passion as THE modern application development style. ### About marketing If you think about it, JPMS and microservices have very similar marketing strategy: - bend core concepts such as modularity and application decomposition to match the solution you have come up with. - pretend there has been nothing like your solution before. - picture anyone not using your approach as old-fashioned or even ignorant. - make fun of and try to discredit anyone who dares to question your decisions. That makes perfect sense from a vendor perspective but it's not doing a favor to the Java community as a whole. I do understand people operate under specific constraints and expectations imposed by the organization(s) paying their salaries. I do understand why they get upset when people who are not bound by the same constrains feel they have the right *(or perhaps even the responsibility)* to point out the facts and question their decisions. Yet I also strongly believe it's in the best interest of the developers community to show the full picture so people can make educated decisions. ### Yes, I know. I can't fix that! I would be stupid to believe that any single individual can. Never the less, I will continue to share my experience with younger fellow Java developers even if that bugs some well known people at well known organizations. I'm still full of respect for their knowledge and achievements. But I'm also full of respect for the knowledge and achievements of other people who dare to disagree with them. So, I will continue to listen to everything all those people have to say and choose to agree or disagree based on my own experience and the challenges I face. I'll continue to speak up about application design principles and show how implementations differ. I am already working on something that would hopefully be practical enough to be useful but at the same time help developers better understand the concepts of modularity and decomposition. Stay tuned, I'll be writing about bits and pieces of that initiative shortly. Finally, please feel invited and encouraged to join me in that journey if you have experience in those subjects and feel in a similar way. # Java EE, EE4J, OSGi, ... and the paradox of choice Not so long ago, I had very interesting conversation with someone who works on Java SE. At some point we discussed the donation of Java EE to Eclipse Foundation. I don't quite remember what statement I was making when I got this response *(not a precise quote)*: > Do you seriously believe this whole EE4J thing has any chance to survive? Oh my god, you are so naive if you do! We sent Java EE there to die! This was a private conversation and I didn't ask nor I was given a permission to quote my interlocutor, so I'm not going to tell you who that person was nor where and when exactly this conversation took place. But the sentence got stuck in my mind. It made me think about OSGi - a technology claimed dead by way too many Java developers. Funny enough, many OSGi projects are developed at Eclipse Foundation. All of a sudden the Eclipse Foundation started to look like a nursing home for terminally ill "used to be famous" Java technologies. ### The symptoms of the disease At first, this thought sounded ridiculous even to me. But thinking more about it, it kind of started to make sense. Ironically, it seams Java EE fell ill with the same "fatal disease" as OSGi. Namely "Too heavy/complex"! Much like in the OSGi case, the diagnose was made based on opinions of young "rock star" Java developers comparing the "old guy" to some "modern" technologies in their Know-It-All phase. But what exactly being "too heavy/complex" means? #### Size Java EE 8 is a [collection of about 40 specifications](http://www.oracle.com/technetwork/java/javaee/tech/java-ee-8-3890673.html){rel=""nofollow""}. This is very similar to OSGi R6 which [consists of 40+ specifications](https://www.osgi.org/developer/downloads/release-6/release-6-download/){rel=""nofollow""}. In both cases this results in hundreds of pages of reading which means learning it all can indeed be challenging and time consuming. Of course in both cases, in any practical scenario, people deal with less then a dozen of those, but the full size gives an excellent argument to pigeonhole them as "too complex". #### Runtime Most implementations of Java EE specifications need an application server runtime. Most of the OSGi specifications need an OSGi runtime. Both technologies have many implementations of the respective runtimes which come from different vendors and vary in size and OOTB functionalities. Many developers would pick the one having the most features OOTB. Just in case or because that's what other developers use. Then they realize they don't need most of them which immediately renders the technology "too heavy". #### Strict rules Java EE has strict rules regarding application isolation and cross-application interaction. OSGi goes further providing even better code isolation by drawing explicit boundaries between modules and making real use of packages. While those have their roots in battle tested software design principles know for decades, they have a significant "disadvantage". They make it extremely hard to practice cowboy style coding *(throw code at classpath and see if it sticks)*. Choosing to understand and play by the rules feels too old school for many Java developers. Fighting against the rules is way more "fun" but eventually results in something "too heavy and too complex". From all of the above symptoms it seams to me the root cause is "too many options", which reminded me of a talk I saw back in 2006 on Google Tech Talks called "[The Paradox of Choice - Why More Is Less](https://www.youtube.com/watch?v=6ELAkV2fC-I){rel=""nofollow""}" by [Barry Schwartz](https://en.wikipedia.org/wiki/Barry_Schwartz_\(psychologist\)){rel=""nofollow""} *(if you don't have an hour to watch it, [this TED talk](https://www.ted.com/talks/barry_schwartz_on_the_paradox_of_choice){rel=""nofollow""} summarizes it well in 20 min)*. ### The paradox of choice In contrast to many "modern" approaches, neither Java EE nor OSGi tell how exactly one should build software. Instead they provide a bunch of proven solutions for well know software problems. This is not surprising considering all those specifications were developed by people representing different companies with different business goals, intending to use the same tools in many different scenarios. They were designed to give developers maximum possible flexibility while enforcing common rules that everyone agrees upon. But that comes at the price of shifting of the burden and the responsibility for decision-making to the application developer. #### Paralysis According to Barry Schwartz, having too much choice leads to decision paralysis. Especially if one lacks crucial information. It's easy to say "I'll use Java EE" or "I'll go with OSGi". But then you have to decide on runtime, specifications, implementations, configuration, security, ... A less experienced developer without good understanding of the technology is easily paralyzed by overwhelming options. The only thing that comes to mind in such cases is "I'll just use what others use". #### Regret That approach also plays the role of safety net in case something goes wrong. "But I chose exactly what this other team uses, and it works for them!" is the ultimate defense strategy and the universal excuse. Yet the more options there are, the easier it is to regret anything disappointing about the option one chose. ### The obvious solution - one size fits all Consider SpringBoot for example. One goes to [start.spring.io](http://start.spring.io/){rel=""nofollow""}, clicks a few checkboxes, downloads ready to run application which is then customized. Yes, there are options but between implementations that are known to work in the well defined environment Spring Initializr creates. No hard choices, no potential inconsistencies and thus no paralysis. No decisions to be blamed for later on. One may dislike something down the road but since there was no better option, one can not be blamed. Forget about flexible, modular, carefully designed applications. Flexibility is achieved by making many "small" applications, each with embedded web server, talking to each other over the network. It keeps surprising me how well that approach sells. Giving up freedom for convenience and peace of mind seams to be a deal everyone is willing to make these days. No, this is no a rant at SpringBoot. It's a general trend in software. Take [MicroProfile](https://microprofile.io/){rel=""nofollow""} as another example. It's nothing more than cutting down the options to the bare minimum and providing convenient tools around the limited set. Think about [JPMS](http://openjdk.java.net/projects/jigsaw/spec/){rel=""nofollow""}. It's basically redefining modular applications and giving up on flexibility in order to trim down the implementation to minimal set of primitives. It is amazing how software developers these days glorify speed, blindly adopt development strategies containing "micro" in the name and treat recourses as they were limitless. ### My dream solution While some people are busy to beg Oracle to allow them to keep the Java EE name and others rush to celebrate the victory of Spring, I for one am keeping my fingers crossed for EE4J project. I hope to see it become totally detached from Java EE, carefully designed set of specifications that serves well to those who value professionalism over speed. Moreover I think it would be awesome if the Eclipse Foundation and the OSGi Alliance can find a way to work together and evolve both EE4J and OSGi in sync to eventually make them fully compatible. I personally wouldn't even mind if they eventually merge under a common umbrella project. Actually some effort towards better interoperability is already in place at the OSGi Alliance. In the not yet released OSGi R7 there is already specification for [JAX-RS Services support](https://www.youtube.com/watch?v=FR_yLECENUo){rel=""nofollow""}. There is also a work in progress on a specification for [CDI Integration](https://www.youtube.com/watch?v=vMdEK5y1hmI){rel=""nofollow""} which will open the door for a variety of other EE4J technologies to be seamlessly integrated as well. It would be great to see EE4J also making a step towards OSGi soon. It can already benefit from nice specifications like [OSGi Promises](https://www.infoq.com/interviews/tim-ward-osgi-promises){rel=""nofollow""} and [OSGi PushStreams](https://vimeo.com/201982439){rel=""nofollow""} which do not require a OSGi runtime. For the last several years I've been working with both Java EE and OSGi at the same time. I've seen their strengths and weaknesses and I always wanted a "best of both worlds" solution. So Eclipse Foundation and OSGi Alliance please make it happen. Because, as they say in Bulgaria, "United We Stand Strong" ! # The fruits of our labor I usually do rather technical [talks](https://MilenDyankov.com/talks) around software architecture and design. Unless one is а famous storyteller, IT conferences would rather take one more *"What's new in the latest version of XYZ technology"* than risk a bet on something that may end up anywhere between boring and sales pitch. I don't blame them but that is why it is not often that I'm given the opportunity to speak about culture, purpose and all those non-measurable, soft, human things. Therefore I'm extremely grateful [Let's Manage IT](http://letsmanageit.pl/){rel=""nofollow""} invited me and trusted me to give exactly this type of talk. Knowing that I'll likely not have the opportunity to present it anywhere else, I decided to convert it into a blog post and publish it here. ### The Orange World project It was mid 2005. My wife and I were driving back home after work when we noticed this huge banner ad occupying the whole sidewall of a rather tall building. It was promoting a new online service called Orange World. ![The Orange World website from 2005](https://MilenDyankov.com/assets/the_fruits_of_our_labor/slide.002.jpeg) The Orange World was a revolutionary approach to mobile entertainment. It was accessible from a mobile phone via WAP, it could recognize the device and offer to the user tons of device compatible ringtones, wallpapers, games, songs, news, ... I'm not joking! Keep in mind back in 2005 iPhone and Android didn't yet exists. Mobile phones were ... well just phones with physical buttons and rather small screens! Mobile internet was just entering the market and WAP was a revolutionary idea. So Orange World was huge and everyone I knew was excited about it! ### Burning money I had an extra reason to be excited - I was part of the team building it! So I started passionately telling my wife about how I was so lucky to work on this project and how it will change the world. She patiently listened while I was explaining how great are the technologies we use *(where are you today [ATG](https://en.wikipedia.org/wiki/Art_Technology_Group){rel=""nofollow""} and [Volantis](https://en.wikipedia.org/wiki/Volantis){rel=""nofollow""}?)*, what challenges we face, how huge the amount of data we process is, ... and the most important thing of course - how much money Orange is investing into it. ![Burning money image](https://MilenDyankov.com/assets/the_fruits_of_our_labor/slide.003.jpeg) I guess I was no less excited than today's startup folks burning VC dollars, so you can imagine my stupid face when she replied with *"What a terrible waste of money! Imagine if they have invested that money into something that actually helps people instead"*. OK, if your spouse is not in IT, do not mention IT projects - that's one lesson I learned. Somewhere deep inside though, I knew she is right. Those millions could have been used in a much better way. Yet, it's not my call. People and companies are free to decide what they spend their money on and at the time I was more than happy Orange made a choice to spend it in a way which allowed me to work on something cool and grow professionally. ### Satisfying the greedy youngster It was because of profitable projects like Orange World, that the company I was working for, was able to take good care of its employees. Good pay, private healthcare, free gym and poll access, bonuses, ... Mentors to learn from, free tech books, wide spectrum of technologies used, ... I was not only able to provide for my family but I also felt I was getting better every day. In another words, I was comfortably occupying the 3rd floor of Maslow's Hierarchy and moving to the one above seemed to be within my reach. ![One of my first public speaking experiences](https://MilenDyankov.com/assets/the_fruits_of_our_labor/slide.005.jpeg) Indeed, in the next few years I moved up in the hierarchy *(both company's one and Maslow's one)*. I was managing a team of about 30 people working on variety of challenging projects. Several big, complex projects for banks, telecommunication and insurance companies, delivered on time and within budget is what it took to earn the trust of the C level executives. Mastering a bunch of Java frameworks was the key to win the respect of the younger colleagues. My professional self-esteem grew high. Together with it, grew this feeling that everything I've achieved was a result of my significant contributions to the *"terrible waste of money"* that large enterprises were so happily investing in! ### The 10x craziness Meanwhile the need for people that write code was increasing every day. As Silicon Valley was growing in size and importance, a new mysterious super-humans ware discovered. They've been called "10x programmers" (a.k.a "rockstar developers"). Many people write code. Not so many people write good quality code. Apparently only those "coding superstars" write good quality code fast *(either 2 or 10 times faster depending on whether you read "10x" in binary or decimal)*. You know, good developers are hard enough to find. But those super-heroes are even 10x harder to find. No surprise talent agencies were established to hunt and sell them. ![A slide from Stephanie Kim's talk “Becoming a 10x Data Scientist“](https://MilenDyankov.com/assets/the_fruits_of_our_labor/slide.007.jpeg) If you represent one of those companies glorifying 10x-ers, I strongly suggest you to take some time to think how you make your x-ers feel. I personally felt really bad about it. Not that I would ever show it, but no matter what I did, I had this feeling I'm not good enough and should be more productive. Being more productive is the ultimate goal of every software developer, isn't it? And than, one day, this colleague of mine, who I thought was one of the few 10x people in the company, told me *"you know, we are just get/set developers. All we do is grab data from one place, change it a bit and store it in some other place."*. It was both relieving and scary at the same time, to discover that I wasn't the only one questioning the sense of our work. Oh and obviously I was wrong, he wasn't 10x-er if he felt like I did, right? ### Autonomy, Mastery, Purpose It was roughly at that time when I discovered the famous *"[The surprising truth about what motivates us](https://ed.ted.com/featured/LT8oQQTo){rel=""nofollow""}"* talk. Just in case you haven't seen it, the bottom line is that once people are paid enough to take the issue of money off the table, there are three factors that motivate them - autonomy, mastery and purpose. ![An illustration of “Autonomy, Mastery, Purpose“ concept](https://MilenDyankov.com/assets/the_fruits_of_our_labor/slide.008.jpeg) I did have some autonomy! Actually for someone working for an about-to-become-a-corporation company, I probably had more autonomy then our competitors would offer to their employees. However the process or replacing autonomy with company rules, was already in place. While I was constantly getting better at programming and software architecture and learning more in general, I felt bad about how restricted I was in sharing that knowledge. It seamed that my only purpose was to help my company grow. But then what was the purpose of the company (*other than making money, that is*)? ### Open Source We were using tons of open source projects but contributing back, while not forbidden, was not really encouraged in any way. I had a few open source projects myself, but those were toys. In 2009 we started using [Liferay Portal](https://dev.liferay.com/){rel=""nofollow""} to speed up the development of content centric web applications. Of course it wasn't my first contact with open source software. I was running Linux on my machine and products like JBoss, MySQL, Spring, Hibernate, ... were well known to me. But this [Liferay company](https://www.liferay.com/en/company/our-story){rel=""nofollow""} - it was somehow different. ![Picture of Liferay's office with “Open Source“ sign highlighted](https://MilenDyankov.com/assets/the_fruits_of_our_labor/slide.009.jpeg) All open source vendors seem to exist for a single reason - make the product popular and then monetize it. I watched JBoss and Hibernate going to RedHat, Spring going to VMware, MySQL to Sun and then Oracle, ... Naturally, everyone was waiting for Liferay to get acquired any moment. These guys were weird though. *"Hey, we’re not looking to get bought. We have no VC funding and we are proud of that."* was what they keep saying. For the next 3 years I got to know better the product, the company and people behind it. It all started to make sense. Liferay had, what my company was lacking - purpose. A purpose I could identify myself with - truly open source, enterprise ready software platform that empowers developers around the world and helps them be super productive. In 2012 I overcame my fear of not being 10x programmer and joined Liferay to help evolve this awesome product and keep it open source so developers all over the world can build awesome things. ### Money If you have ever changed jobs you know that feeling when your original idea of what the new company is all about meets the reality. No exceptions this time. Not so long after I joined the company there was this event at which Bryan Cheung, Liferay's CEO, clearly stated *"When we started Liferay, we wanted to build a company that would make a lot of money ..."*. ![Image showing money](https://MilenDyankov.com/assets/the_fruits_of_our_labor/slide.010.jpeg) OK, I can almost hear you laughing and saying *"I knew it"*. And yea, my first though was *"Of course! How can you be so naive?"* I felt I was such a fool to believe they actually care about open source and developers and freedom and all that *"ideological nonsense"*. For a second I felt so disappointed I almost missed the second part of that sentence: *"... to give a lot of money away."*. Trust me, I wasn't less surprised than you are now. As weird as it sounds it explains why Liferay founders didn't want any external funding and worked hard to grow the company organically. ### People So I was wrong. It wasn't only about open source. It was about much more than that. As I learned over time, Liferay sets aside 10% of the profits for giving back to those in need. Obviously it is not the only company that donates money, but it is the only software vendor that I know of, that was created with that purpose in mind. Someone has come up with an idea to actually turn that *"terrible waste of money"* my wife was talking about, into *"something that actually helps people"*. And it was working! ![Picture of Liferay's office with “For Life“ sign highlighted](https://MilenDyankov.com/assets/the_fruits_of_our_labor/slide.011.jpeg) Probably most developers joined Liferay because they have a passion for technology and open source and because Liferay is a really good employer. For many, those are good enough reasons to join and stay with the company and contribute to the next open source components providing better business solutions, barely noticing the nobel goal in the background. Indeed, the fact my company had greater purpose made me feel much better about what I was doing. Yet, it is hard to imagine what impact your work have on another person's life if all it comes down to, is a money transfer made by your employer. But as it turns out, there is a way to reach the human inside the employee. #### Bikes for Burma So, imagine a company gathering that, gets the people together in a tent for some team building activities. ![Picture from "Bikes for Burma" event](https://MilenDyankov.com/assets/the_fruits_of_our_labor/slide.012.jpeg) Say people are divided into 30 groups and each has to put together a kids' bike from the provided parts. ![Picture from "Bikes for Burma" event](https://MilenDyankov.com/assets/the_fruits_of_our_labor/slide.013.jpeg) All bikes are to be donated to a local nonprofit organization that will deliver them to refugee children from Burma. ![Picture from "Bikes for Burma" event](https://MilenDyankov.com/assets/the_fruits_of_our_labor/slide.014.jpeg) Once they are done, the leader of the nonprofit organization goes on stage to thank everyone. He concludes by saying that the best way to understand how much this assembly work means for those poor kids is to be able to see their faces when they receive the bikes. ![Picture from "Bikes for Burma" event](https://MilenDyankov.com/assets/the_fruits_of_our_labor/slide.017.jpeg) Then the door opens and the kids and their parents get in the tent to receive the gifts. ![Picture from "Bikes for Burma" event](https://MilenDyankov.com/assets/the_fruits_of_our_labor/slide.018.jpeg) #### Clean water in Guatemala Imagine that later on a group of employees join a non-profit organization that helps communities in developing countries create sustainable water and go to the green valleys of Guatemala to help the local drilling team build a well for a community desperately in need of clean water. ![Picture of the well in Guatemala](https://MilenDyankov.com/assets/the_fruits_of_our_labor/slide.021.jpeg) #### The Water Walk Imagine there is a software conference which has a lighted path and two 40lb (18kg) jugs of water. Whenever someone carries the jugs to the end of the lighted path and back, so they get to understand what in many corners of the world, children have to do every day, a $30 donation is made to [charity\:water](https://www.charitywater.org/about/){rel=""nofollow""} to build wells in developing nations. ![Picture from Liferay North America Symposium](https://MilenDyankov.com/assets/the_fruits_of_our_labor/slide.024.jpeg) ### EVP You've guessed right, all these already happened at Liferay and gave me and many of my colleagues a purpose not found in other software companies. But then the next question arises - who decides who will get the help? Of course for each of us there is slightly different thing that matters most. And people do their best when they do things they deeply care about. It turned out there was a solution for that problem too. It's called Employee Volunteer Program (EVP) and I was watching it grow and expand worldwide over the last few years. ![EVP's home page](https://MilenDyankov.com/assets/the_fruits_of_our_labor/slide.025.jpeg) EVP allows employees to realize their own passions for making impact both in their local communities and worldwide. EVP gives each full-time employee up to 40 paid hours per calendar year to be used to provide service related to disaster relief, providing food, water and shelter, basic health and education, vocational empowerment, freedom and justice. EVP also allows full-time employees to request up to 500 EUR in combined grant funds per calendar year to be distributed to a nonprofit organization. Here are just a few things EVP made possible: ![Map showing places on earth where people have participated in EVP](https://MilenDyankov.com/assets/the_fruits_of_our_labor/slide.026.jpeg) - Liferay employees traveled to Haiti after the devastating 2010 earthquake to help rebuild. - Liferayers traveled to Treme, New Orleans as Liferay's first group service team to help rebuild after Hurricane Katrina. - Liferay employee was able to support and encourage his friend contracted with a very large brain tumor. - Several Liferay teams have traveled to Japan, to help in rebuilding houses and businesses after devastating earthquake and tsunami in 2011. - Liferayers traveled to New Jersey to help with relief work after Hurricane Sandy devastated the East Coast in 2012. - Liferay Dalian's employees went to Wenshan, China in 2014 and 2015 to bring joy and fun to special needs children and their families. - The Liferay Brazil office collected 700 kg (1543 pounds) of food to help poor families in the state of Pernambuco. - Throughout 2015, Liferay sent monthly teams to serve at Skid Row - an LA neighborhood that has one of the largest homeless populations in the US. - Members of the Liferay Madrid office partnered with local nonprofit to refurbish a house that will be used as a shelter for victims of human trafficking. - Two Liferay Support engineers conducted a five-week coding workshop to introduce a group of inner-city kids to the wonderful world of programming. - The Liferay Brazil office helped build and open an independent library with 2000 books - Australia team hosted a family Christmas party for local children and their families. - Liferay LA team partnered with nonprofit organization to renovate a thrift store that serves women rescued from trafficking - A Liferay employee traveled to Greece to serve Syrian refugees who are waiting for entry into the EU. - A Liferay employee's family has grown from five to eight through adoption. ![Stats from EVP's web site](https://MilenDyankov.com/assets/the_fruits_of_our_labor/slide.027.jpeg) At the time of writing, EVP has been active for *8 years*. During this time *622 employees* have participated resulting in *18272 service hours* and *371318 EUR grant money* distributed to *408 nonprofits*! ### Why am I telling you all this? Liferay is not a charity organization but for-profit software company. As such is suffers from all the issues present is such companies. People disagree with each other, miscommunicate, make mistakes, have to make hard choices, need to keep up with ever changing software trends and customer expectations, have to keep deadlines, sometimes sacrifice things they value ... oh, you name it. The amount of things I've been frustrated with in the last 5 years, would normally have been more than enough reason for me to leave. Before Liferay I was very good at arguing. I practice that skill here as well but I'm now learning to listen, understand, forgive, rethink and try to work things out in an alternative way. ![A quote by Theodore Roosevelt “No one cares how much you know, until they know how much you care“](https://MilenDyankov.com/assets/the_fruits_of_our_labor/slide.029.jpeg) I have autonomy, I get better at things and I don't have the feeling my time is wasted on a code that is sentenced to grow old and forgotten in a private repo. Quite the opposite, I meet people from all over the world who use our software to build beautiful things and perhaps earn a living. I see how "more profitable" translates to "more smiling faces in some forgotten corner of earth". When I quit my previous company I was sad because of the good friends I left behind. Quitting Liferay would mean not only that but also giving up on a great purpose. ![Liferay's office wall saying “to see people reach their full potential to serve others“](https://MilenDyankov.com/assets/the_fruits_of_our_labor/slide.030.jpeg) But I didn't spend all this time to give you an advice how to hook on people's deep feelings so they don't leave your company. There is by far more important aspect - what type of developers you cultivate in your organization. When I was junior developer, the worst thing an irresponsible programmer could cause was some money loss *(well, NASA programmers excluded)*. Today we have planes, cars, drones, medical equipment, ... and million other things powered by software. Plus social networks that can shape not only people's purchases but also their voting habits. Careless developers could agree to write code that does illegal things *(like cheating about CO₂ emissions)*, involuntarily kill people or even cause mass riots. At the same time, many companies still chase the mythical 10x coders and try to come up with the best way to measure developers productivity. So here is my proposal. Let's forget about rockstar programmers and try human programmers instead. Let's value developers who care about humans more than *(or at least equal to)* those who care only about the code. Then may be, just may be, we can start thinking of how to answer Bryan's question! ![Photo of Bryan Cheung's slides saying “What if lines of code you've written == lives of people you've changed?“](https://MilenDyankov.com/assets/the_fruits_of_our_labor/slide.031.jpeg) # Fire alarm at software conference I guess if you do something often and long enough, you get to experience all possible scenarios eventually. Leaving the conference venue due to fire alarm going on, was not necessarily on my bucket list but now I can both add it and scratch it off at the same time. It happened last week in Malmö, Sweden. OK, I need to apologize now. The fire alarm thing was just a cheap trick I borrowed from newspapers' headlines to grab your attention. It is NOT a lie though, it indeed happened and I'll tell you about it at the end, but that is not what I really want to tell you. I'm writing this because I was really impressed by a few things at [Øredev Developer conference](http://oredev.org/2018/home){rel=""nofollow""} and whether you are conference organizer a speaker or just looking for conference worth attending, it may be of some interest to you. ### Organization Like many good software conferences Øredev was super kind to cover travel and accommodation costs for the speakers. They were extremely patient with me while I was waiting to resolve a travel overlap with another event. Unlike any other software conference I've been at, they also made it super easy for the attendees to travel within Malmö during the conference days. And by "super easy" I mean no special tickets, no fancy mobile apps, just use your conference badge! ![The back side of Øredev badge](https://MilenDyankov.com/assets/2018-11-24-fire_alarm_at_software_conference/badge.jpg) Kudos and thanks to [Emily Holweck](https://twitter.com/Emily_Holweck){rel=""nofollow""}, [Tadeáš Peták](https://twitter.com/tadeaspetak){rel=""nofollow""} and Amelia Barklid for organizing such an awesome event. ### Speaker dinner at Malmö City Hall (Rådhuset) As you can deduct from the above section, Øredev has clearly made some nice arrangements with the city of Malmö. My first impression that the local authorities understand the impact the event has on the local economy and tourism, was confirmed by Malmö’s mayor who welcomed the speakers at the speaker dinner at [Malmö City Hall (Rådhuset)](https://www.guidebook-sweden.com/en/guidebook/destination/malmoe-radhus-historical-town-hall-in-malmoe){rel=""nofollow""} ![Malmö’s mayor welcoming the speakers](https://MilenDyankov.com/assets/2018-11-24-fire_alarm_at_software_conference/mayor.jpg) It was amazing! It was also somewhat hilarious at the same time to observe few ladies in evening dresses and high heels surrounded by bunch of dudes in hoodies with company logos on them ;) ![Speaker's dinner](https://MilenDyankov.com/assets/2018-11-24-fire_alarm_at_software_conference/dinner.jpg) ### Recommended talks Øredev is a big event with 7 tracks. But it's doesn't seem to be the "usual suspects" type of conference where "the big names" are invited to give a variation of the same talk year after year. I was pleasantly surprised by the great talks I was able to attend presented by very knowledgeable yet not-so-famous speakers. When a program committee has done a great job like that, they fully deserve to have their names on the wall so people can know who they are and say "Thank you!" to them. And with over 150 talks, it can be really hard to decide which one to go to. To help with that, some program committee members have posted their own trails. ![Program committee and recommended talks](https://MilenDyankov.com/assets/2018-11-24-fire_alarm_at_software_conference/talks.jpg) Of course, as a speaker, you hope to find your talk in one of those trails and it's a bit disappointing when you realize it's not there. But then, when people fill up a room called "Cryogenic Chamber" regardless and press the green smiling face on leaving it, it's even bigger satisfaction. And since this is the section about talks, let me tell you which ones I personally enjoyed the most: - [Lean vs Agile vs Design Thinking](http://oredev.org/2018/sessions/lean-vs-agile-vs-design-thinking){rel=""nofollow""} by [Jeff Gothelf](https://twitter.com/jboogie){rel=""nofollow""} - [The Psychology of Social Engineering](http://oredev.org/2018/sessions/the-psychology-of-social-engineering){rel=""nofollow""} by [Niall Merrigan](https://twitter.com/nmerrigan){rel=""nofollow""} - [Beyond Conway's Law: Meet the Social Side of Your Code](http://oredev.org/2018/sessions/beyond-conway-s-law-meet-the-social-side-of-your-code){rel=""nofollow""} by [Adam Tornhill](https://twitter.com/AdamTornhill){rel=""nofollow""} - [Finding your service boundaries - a practical guide](http://oredev.org/2018/sessions/finding-your-service-boundaries-a-practical-guide){rel=""nofollow""} by [Adam Ralph](https://twitter.com/adamralph){rel=""nofollow""} ### The logbook It's not the first time I see conference printing the schedule in a book format. But the ones I've seen so far were kind of a guide books. At Øredev it was more of a logbook. Small change in the concept apparently makes a big difference. The idea to have a dedicated page per time slot, where attendees can make notes about the attended session, seemed to work out very well for a lot of people. I was surprised by the amount of people actually taking notes during the talks! ![The guidebook](https://MilenDyankov.com/assets/2018-11-24-fire_alarm_at_software_conference/guidebook.jpg) ### The speaker gift In the past 4 years I've received all kinds of gifts from conference organizers. From power banks and tiny bluetooth speakers, through nice books and travel guides to bottles of wine and olive oil that are impossible to bringing back home without checking in the luggage at the airport. At Øredev I was given a big white envelope which, being busy talking to some attendees, I couldn't open immediately. Imagine my surprise later on that day when I discovered **one of the best gifts I've ever received** inside: ![The guidebook](https://MilenDyankov.com/assets/2018-11-24-fire_alarm_at_software_conference/gift.jpg) ### Oh yea, the fire alarm ;) Well, as I said, it did happen. During the evening reception. There wasn't actually a fire though. But something (perhaps the fog machine) triggered the alarm and we had to get outside and wait for the fire department to come and let us back in. There was no panic, no one got hurt (AFAIK) and in was only about 15 min before we were back in. I personally used those 15 min (and more later on) to have a great discussion with [Martin Rosén-Lidholm](https://twitter.com/rosenlidholm){rel=""nofollow""} and learn about the personalities described in apparently controversial ["Surrounded by idiots"](https://thomaserikson.com/en/books/){rel=""nofollow""} book. # Would you attend one of those talks? After several years of traveling around the world to speak at conferences, I needed a break. I have no idea what people like Venkat Subramaniam or Josh Long or Philipp Krenn are made of, but the assembly line that made me, certainly didn't use the same material. Luckily 2019 offered me that break and allowed me to focus on other things. It was great time but also kind of sad as I like to share the little things I know with other people and I like even more to learn from the people I meet. So I'm planning to be back on the road in 2020. But before that **I need a little help from you in the form of your honest feedback**. I've prepared 3 talks that IMHO should deliver some value to fellow programmers. But for some reason (perhaps the above mentioned break) I'm not very confident this time. If you have seen my previous talks you are aware that I'm not the "let me show you this cool new thing" kind of presenter. I really want my talks to share experience rather than hot news and to provoke (most often critical) thinking rather than be a standup version of a tutorial. But that makes it extremely hard to reliably self-judge their attractiveness, let alone the value added. In addition to that, most conferences don't provide any feedback why they accept or reject proposals (even though they collect them months in advance). The whole C4P experience feels more like buying a lottery ticket than subjecting your work to substantive assessment. So this is where I need your help. Below, I'll share with you the talk titles and descriptions I have prepared. Please have a look and let me know in the comments what is you honest oppinion. In other words, if you ware reading those descriptions in the agenda of your favorite conference, how likely are you to attend one of them? Which one? Oh and it would be good to know what is your favorite conference (where those would be good fit)? Don't be afraid to criticize them (or me) if you feel you should. Any ideas for improvements or things to highlight/include/avoid/be careful about/... are also more than welcome. Whatever feedback you can provide, no matter how small, matters a lot to me. **Big thank you in advance for being willing to sacrifice a few minutes of your precious time**. Here we go: --- #### From portals to micro-frontends (and back?) Years go by, and backend programmers are still busy smashing down the bad monoliths, putting the pieces in beautiful containers and ballooning them up in the cloud. The frontend folks got somewhat jealous and figured out a way to smash down their own big pile of HTML/JS/CSS. And they too have a cool name for it - micro-frontends. Both groups try hard to forget (or don't care to learn) we've done this before! In this talk we'll explore a tiny bit of history of a (now considered legacy) concept that was supposed to solve much the same problems - portals. We'll try to understand why such a great idea (which it was decades ago) didn't work out? What problems and challenges it faced? How likely we are to face those again? Can we learn from the past experience? Or better yet can we combine it with modern technologies to achieve even better results? Join me, learn the history, see the demo, and decide for yourself! --- #### Writing Java with Coherence, Coupling and Connascence in mind You just saw 3 words that seem important enough to make you read the description. Good. If you are like the so called "average Java developer", you probably have hard time giving the exact definitions of at least two of them but you will never admit it. I don't blame you. I've been there myself. That's how I know you are likely to believe that you somehow naturally and subconsciously already apply those principles in your programs. This talk will try to make those terms less abstract and help you associate them with practical examples. We'll examine not only the Java code itself but also some conventions, libraries, frameworks, build tools, dependency management systems, ... that encourage/discourage or enforce certain practices. The final decision about what is "good" and what is "bad" is still yours of course, but after this talk, you'll be able to base the call on much more tangible data. --- #### Promises for resilient Java APIs not throwing Exceptions There are several reasons to attend this talk. Perhaps you like the Promises concept in JavaScript and wish Java had it? Or may be you were excited about CompletableFutures but got disappointed by the bloated API? Or you hate having to catch tons of exceptions and you want your API to not irritate your consumers as much as other APIs irritate you? Or you hope to finally understand what a Monad is? OK, just kidding, the "no one can explain monads" rule still stands. But all the previous ones are true and serious. So allow me to introduce you to the Promises specification and demonstrate in practice the reference implementation of it. This tiny (12 classes total), dependency free, monadic library can be a real lifesaver for the above mentioned scenarios and many more. Once you see it in action, questions like "what this method should return?" or "should it throw checked/unchecked exception?" will almost never bug you again. --- # Conference Tracker I try hard to keep track of conferences around the world. Mainly in a Google spreadsheet but also in a calendar shared with my team. I always thought that if all the DevRel folks were to share and merge their spreadsheets (or whatever else they use) into one single place, it would literally save days of work. As Liferay Portal (the product I mainly work with) transitions more and more into headless backend for React, Angular, Vue.js, ... based application, I felt it's time to get out of my Java comfort zone. I thought it would be nice to build a SPA that is more complex than "Hallo World", simple enough to be build in days rather than weeks, relatively good looking (for a non-designer like me) and ideally useful to someone. And then something happened ... Love at first sight normally occurs to souls, but this time it enamored two ideas! ### TL;DR A love story like this deserves to be described with a few more words, but if you really want the spoiler, be my guest. The baby is called [Conference Tracker](https://milendyankov.com/ConferenceTracker){rel=""nofollow""} and it looks like this ![Conference tracker screenshot](https://MilenDyankov.com/assets/2019-12-02-Conference_Tracker/conference_tracker.png) ### The inner beauty I really don't want to waste your time with my struggles to compare and evaluate JavaScript Frameworks. All I'll say is that of the 3 major ones I found [Vue.js](https://vuejs.org/){rel=""nofollow""} to be one I can almost instantly understand and what is more important, it does not make my thinking-in-modules mind sacrifice too much. Surprisingly (to me) it turns out one can have well structured, modular SPAs. Making them petty though is a whole different story. I'm generally not afraid of CSS but styling everything from scratch is not something I'm excited about. So I spent some time searching and I found [Quasar](https://quasar.dev/){rel=""nofollow""}! It comes with very good set of components, tools and extensions, making the applications look nice from the very begging. And it aparently can build SPA, SSR, PWA, Mobile, Cordova, Electron and bunch of other magical (to me) types of applications from the same source code. I'm yet to experiment with those. If you are a backend developer like me, by now you probably have hard time refraining from yelling "cut the crap and tell me about the backend". Well, here comes the disappointment - there is no backend. At least not one that I have built. As I already told you, all data is in a [Google spreadsheet](https://docs.google.com/spreadsheets/d/1UEXmLwp8qEvvwBjiNQGSAB07QFSPVgD-10ieljAnevg/edit?usp=sharing){rel=""nofollow""} and the SPA gets it directly from there. It's not the most efficient way on earth to store data but it has its advantages - it allows people to collaborate on the data without the need to build and maintain custom backend. So please feel invited to ### Join the party The spreadsheet is available to anyone in "comment only" mode. If you see mistakes, please add a comment. If you want to add a conference to it, use the [Google Form](https://forms.gle/vFcWJKWtqD7NrxmZ8){rel=""nofollow""}. I'm willing do give write access to trusted editors, so if you think you should be one of them, drop me a line. I consider this an experiment. I'm curious to see if the result of my learning exercise can evolve into something bigger. I have tons of ideas where to go from here, but I don't really want to invest my time in the unknown. So if you like it and you can see how Conference Tracker (or whatever it becomes) can deliver even more value to you, do not hesitate to drop me a line. # Mind the involuntary observational learning Today I want to talk about something that has been bothering me for quite a while now - the side effects of how we teach people to use frameworks, libraries, tools, ... It's an issue I've observed a lot over the years. To the point I have somehow trained myself to immediately notice it when I see it. It's present in conference talks, blogs, articles, video tutorials, code samples, ... virtually any type of learning material that shows Java classes in packages. A lot of those materials are created by people whose knowledge and intentions are unquestionable. I'm full of respect for the great work those folks do. Yet I can't help but notice how many of them involuntary introduce to young developers a very bad practice. I really don't want to be the moron who noticed something not quite right and is now rushing to criticize. Sadly my experiences shows people often feel this is the case when given negative feedback. So it's been hard for me to point fingers at the (otherwise great) outcome of people's hard work. ### The observation Here is a screenshot of the recording of otherwise amazing talk at [Liferay DEVCON 2019](https://www.liferay.com/web/events-devcon-recap){rel=""nofollow""} by a friend and colleague. Take a closer look at the structure of the source code displayed. Is there something that bothers you? ![Screenshot of talk recording](https://MilenDyankov.com/assets/2019-12-14-Mind_the_involuntary_observational_learning/talk-screenshot-anonymous.png) I don't know about you but I get goose flesh when I see package names like those: - `___.___.controller` - `___.___.dto` - `___.___.service` - ... I've seen very similar structures in `Spring`, `Jakarta EE`, `MicroProfile` and even (shockingly) some `OSGi` tutorials, articles, posts, talks, ... ### The issue I do understand where that comes from. Those materials are created to teach a particular technology. They concentrate on the actual code inside the classes. Where the classes are placed is irrelevant to the actual learning experience. Moreover grouping them this way makes it easier to the reader / observer to focus on particular phase of the learning path ("here we talk about controllers", "here we talk about DTOs", ...). Of course the authors don't promote nor emphasize such package structure. The assumption seems to be that everyone understands that what they see is just a demo code and people would somehow "do it right" in the "real world" case. I would however argue that, an involuntary observational learning causes that, **such examples make the wrong patterns stick inside people's minds**. Later on developers use them in production code convinced they follow a best practice as demonstrated by famous speaker or official tutorial. Then other people follow their example and the problem grows exponentially. This contributes to the fact that packages are likely Java's most ignored, misunderstood and misused concept. ### Wait, what's wrong with that example To understand why this is so wrong, imagine we don't talk about `packages` but `jar files` instead. Imagine that you develop an application and you put all your controllers in one jar file, all your DTOs in another jar file, all your services in yet another jar file, ... You get the picture. And it probably looks ridiculous and makes no sense to you, right? After all, those jar files will be so tightly coupled that you will almost always need them all together. Technically it would be no different from putting all of them in a single jar. The only thing the separation provides is some extra "jar chasing fun" to the person running the application. It's not any different with packages. **Packages must always group classes in a coherent way**. Packages in libraries must also provide **strong encapsulation**. Packages should always be designed to appear coherent to the consumer (reader) not the producer. Back to the code on the screenshot above, if as a consumer I'm looking for say `AplicantActionControler` the coherence that I'm expecting is most likely "*classes about applicants*" and not "*classes that are controllers*". When we talk about libraries, a second dimension of segregation is needed. In addition to the coherence, packages should also hide internals and expose interfaces. While Java developers building OSGi based applications are well aware of that, many others only begin to discover the importance as they adopt Java Platform Module System (JPMS, a.k.a Jigsaw) in recent Java versions. As the adoption of JPMS increases, so will the need to package classes properly. ### How to fix it If I was preparing the above demo, my package structure would have rather been something like this: - `___.___.applicant` - `___.___.applicant.internal` - `___.___.terms` - `___.___.terms.internal` - `___.___.shared` - `___.___.shared.internal` - ... I'd argue we should re-enforce the message about the importance of coherence and strong encapsulation on every occasion. No matter if it is tutorial, demo, PoC or production code. If we are serious about teaching people to write good code, we must take no shortcuts as those will inevitably be seen as patterns. I'd rather have people confused about my usage of packages and ask me questions about it, than have them assume it's OK to just throw classes in a packages named after some (irrelevant from business perspective) technical characteristic. ### It's not only about packages And it's not only about bad examples. Missing context is almost as bad. Modularity for example is often ignored concept in many resources, thus convincing people it's not important. Software architecture and application design suffer from the same side effects: > Just because we're not saying something in our talk, doesn't mean we don't think it's important. > :br:br People seem keen to read into what's not said - "they didn't explicitly mention design, therefore we shouldn't do design" > [@simonbrown](https://twitter.com/simonbrown?ref_src=twsrc%5Etfw) > [#yow19](https://twitter.com/hashtag/yow19?src=hash&ref_src=twsrc%5Etfw) > > — Andy Palmer (@AndyPalmer) [December 6, 2019](https://twitter.com/AndyPalmer/status/1202744513850044416?ref_src=twsrc%5Etfw) We kind of learned the lesson in the case of security though. Most content I see these days has some kind of warning stating that what you are looking at is just a demo/sample and is not secure the way it is. They often point out some potential risks and strongly encourage people to learn about security before they apply what they have just learned on production. Can we all please do the same for software architecture, application design, modularity, packaging, ...? I'll conclude this post with a request to my fellow speakers, bloggers, technical writers, trainers and in general people who teach other people. **When working on a demo/sample code, please watch out for patterns, or lack thereof, that predispose to involuntary observational learning of bad habits**. # Data classes in Java I recently joined [AxonIQ](http://axoniq.io){rel=""nofollow""} to help them evolve their Developer Relations to the next level. One of the things I am currently evaluating is the steepness of the adoption curve of [Axon Framework](https://axoniq.io/product-overview/axon-framework){rel=""nofollow""} and [Axon Server](https://axoniq.io/product-overview/axon-server){rel=""nofollow""}. One of the things that catch my attention was that, in almost all examples and demos, the classes representing events, commands and queries are written as Kotlin data classes ([here is an example](https://github.com/AxonIQ/giftcard-demo/blob/master/src/main/java/io/axoniq/demo/giftcard/api/api.kt){rel=""nofollow""}). It got me thinking and last week I put this poll on Twitter > Say you work on a project primarily written in [#java](https://twitter.com/hashtag/java?src=hash&ref_src=twsrc%5Etfw) and it needs a lot of data classes (only fields and methods for accessing them). What would you use to avoid the boilerplate code? Please RT for reach. > > — Milen Dyankov (@milendyankov) [August 17, 2020](https://twitter.com/milendyankov/status/1295411395874435072?ref_src=twsrc%5Etfw) The tweet received some comments and suggestions about solutions and approaches I wasn't aware of. I thought that may be the case for other people too. So in this post I'll talk about the options one has to implement a data classes in Java (and JVM languages). I hope it'll help readers pick the right approach for their own use case. ## What are data classes anyways As you are probably well aware, Java is a class-based, object-oriented programming language. Therefore everything is an object (instance) of given class. In class-based OOP a class has data (fields) and behavior (methods). The idea that objects/classes have behavior is such a fundamental one that even the most basic types like `String` have tons of it (there are methods to split, concatenate, match, convert, ...). Sometimes though you just need to pass some data from one place to another and it comes handy to have a class encapsulating that data. There is no behavior, just the data. That's what's the term data classes usually refer to - classes that only have fields. Theoretically that's totally fine. Nothing is OOP or Java forces you to add behavior to your classes. Until there is. ## The assumed and expected "behavior" While your objects don't have any behavior that matters, the environment (other Java classes, libraries, frameworks, ...) may assume or expect one. The obvious example here are the famous `equals()` and `hashCode()` methods. They have default implementations so theoretically you may not bother implementing them. But anything outside your classes that needs to tell if two objects are the same, would expect you to implement them properly. There is no easy way to tell Java "these data objects are equal if all of their fields are equal". Same goes for the hash key which is crucial when your data classes are put in a `Map` for example. In addition to that, there is the widely adopted [JavaBeans](https://en.wikipedia.org/wiki/JavaBeans#JavaBean_conventions){rel=""nofollow""} convention according to which all fields should be private and have getter and setter methods. You may not care much about that (and honestly speaking, you probably shouldn't) but if you are at the mercy of some frameworks that require beans or assume everything is a bean, then you do have some boilerplate code to write. Rarely there may be cases when something would need to (re-)create, deserialize or make a copy of your data objects. That something may have it's own expectations about the class's constructor(s). ## Automating the boilerplate code The above assumptions and expectations are what often transform an easy task into annoying one. Writing boilerplate code is not fun and may be time consuming. In the data classes context, there are several ways to automate that. I'll present them below with some of their pros and cons. ### Use Lombok's @Data The winner in the poll is not a surprise. [Lombok](https://projectlombok.org/){rel=""nofollow""} comes with tons of other goodies that Java developers love. To crete your data class with it just add the fields you need, annotate it with [`@Data`](https://projectlombok.org/features/Data){rel=""nofollow""} and let Lombok do the rest for you: ```java @Data public class User { private String name; private Integer age; } ``` The really cool thing about this approach is that it does not modify your source code. Lombok is essentially an [annotation processor](https://openjdk.java.net/groups/compiler/processing-code.html#processor){rel=""nofollow""} that enhances the compiled classes directly. Therefore when you edit the file you only have the important data. All the boilerplate is added behind the scenes. There are few downsides of that approach though. First, you don't see the generated code. It's probably not an issue for simple data classes but it may be for more complex ones. I've heard of projects who run into serialization/deserialization issues with Lombok generated classes. Another inconvenience is that by default IDEs will not be aware of Lombok. They will not run the annotation processor(s) and fail to compile your code. You'll have to configure your IDE's annotation processing options or install a Lombok plugin. There is also one false drawback that is often brought up. Namely that Lombok is an additional dependency that you need to ship with your app. That's not true. Lombok is required at compile/build time only. It's not needed at runtime. Your application may use Lombok to produce a binary and then run perfectly fine without it. That said, if you introduce it, everyone (humans and systems) building the project would have to be able to work with it. Only in that sense it is an additional dependency. ### Use the IDE's code generation capabilities Every IDE I'm aware of, has the ability to generate `equals()`, `hashCode()`, `toString()`, getters, setters and constructors. It may require a few mouse clicks and or remembering a few shortcuts but it's still way faster than typing all that code. Some IDEs will even allow you to provide your own templates for the code generation so that you know exactly what the code will look like. The downside of this approach has is that you still will have all this boilerplate code in front of your eyes every time you look at the class. It may be harder to keep it clean and consistent when different people on the same team use different IDEs or differently configured code generators. In such cases the codebase tends to become messy over time with each data class using slightly different approach. It's also may be hard for newcomers to recognize those as data classes and before you know they may start adding behavior to them. ### Use Kotlin's data classes Significantly reducing the boilerplate is one of the things most JVM languages pride themselves with. Kotlin - a JVM language whose popularity skyrocketed after Google announced it's the preferred language for Android apps - is no exception. Its [data classes](https://kotlinlang.org/docs/reference/data-classes.html){rel=""nofollow""} provide conceptually the same functionality you get with Lombok but with even simpler syntax: ```kotlin data class User(var name: String, var age: Int) ``` Furthermore Kotlin allows you define multiple data classes in a single file (as you saw in the [Axon Framework's example](https://github.com/AxonIQ/giftcard-demo/blob/master/src/main/java/io/axoniq/demo/giftcard/api/api.kt){rel=""nofollow""} above) which is super convenient. If you already know Kotlin, that's probably as easy as it gets. The drawback is - you are mixing languages. That means you need to add a Kotlin compiler to your development/build process. If adding Lombok as build time dependency concerns you, then picture adding a whole new language stack to the project. If not knowing what Lombok generated concerns you, imagine relying on a whole different language with own assumptions and priorities. ### Use classes with public fields If you don't care about comparing objects and 3rd party's expectations, that's probably the best option. Personally that's my favorite approach for "passing data around" scenarios. Just make the fields public and forget about getters, setters and even constructors: ```java public class User { public String name; public Integer age; } ``` It fact OSGi's [Data Transfer Objects (DTO) specification](https://docs.osgi.org/specification/osgi.core/7.0.0/framework.dto.html){rel=""nofollow""} describes exactly that - an object with public fields and no behavior that represent the state of a related runtime object in a form suitable for easy transfer to some receiver. ### Use Immutables A few people pointed out [Immutables](http://immutables.github.io/){rel=""nofollow""} as their preferred solution so I though I should mention it even though I have never used it myself. From the docs it seems to be an annotation processor similar to Lombok with heavy focus on immutability: ```java @Value.Immutable public interface Person { String name(); Integer age(); } ``` If I understood the concept correctly the main difference would be that it'll by default generate an immutable class and a builder for it. Something you can also do with Lombok if you want to, by using [`@Value`](https://projectlombok.org/features/Value){rel=""nofollow""} and [`@Builder`](https://projectlombok.org/features/Builder){rel=""nofollow""} annotations. I have the feeling the pros and cons here are exactly the same as in Lombok case described above. ### Use records (requires Java 14 or newer) A possible, long awaited, official solution to the data class boilerplate problem may have finally arrived to Java 14 thanks to [JEP 359: Records](https://openjdk.java.net/jeps/359){rel=""nofollow""}. As you can see the syntax looks a lot like the Kotlin one: ```java record Person (String name, Integer age) {} ``` That's another solution I have no experience with, so I'll refrain from speculating about pros and cons. At the time of writing, this is still a preview feature in a non-LTS Java version. You shouldn't be using that in production systems just yet. By the time it's production ready, it may look or behave differently. That said, it seems some unicorn projects are happy with it. I'm sure there will be tons of articles about it in the upcoming months. ## Summary It all boils down to how nice you want to play with the expectations that 3rd parties may have. Public fields is by far the simplest and cleanest way if you don't care to comply. If you do, you'd have to pick one of the other options. I just wanted to give you an overview so you know what's out there. I'm not picking one for you nor rating them. For the case of [Axon Framework](https://axoniq.io/product-overview/axon-framework){rel=""nofollow""} and [Axon Server](https://axoniq.io/product-overview/axon-server){rel=""nofollow""} samples and demos, I think we should find a way to demo the products using all fundamentally different possibilities (IDE generated, annotation processors based, modern JVM language based, public fields) to not give the false impression we require or favor one approach over another. # What dev heck? There are a lot of publications explaining how crucial DevRel (Developer Relations) and DX (Developer eXperience) are for software vendors. It seems more, and more of them establish such teams. The goals they hope to achieve probably vary a lot. Yet, according to the 2021 edition of [State of Developer Relations](https://www.stateofdeveloperrelations.com/){rel=""nofollow""} report, most often, such teams report to marketing. Is DevRel a fancy name for developer-focused marketing then? There are also plenty of posts about what the Developer Evangelist and Developer Advocate roles are about. One can sense the struggle to convince others that those are actual professions and need to be appreciated as such. There is always (at least indirect) relation to revenue generation. It's like those folks feel guilty that what they do, does not directly translate to dollars or euros. I can't help but wonder why that is. I mean, do you know an HR professional or an accountant worried that their work does not generate revenue? So this post is my take on the whole DevRel, DX, Evangelism, Advocacy, ... thing. I've spent over 10 years on the field and have a "strong" opinion that I want to share with you. That does not make me right about any of it. My experience shaped my opinion. I understand yours may be completely different, so you are more than welcome to disagree with me. ## TL;DR No worries. Feel free to jump to the [summary section](https://MilenDyankov.com/#summary) at the end! ## Developer Evangelism That was the first term that I encountered. It was back in circa 2002. I attended an event organized by Microsoft, and I met a few friendly and knowledgeable folks representing the company. Later on, I discovered they were “Developer Evangelists”. A red light went on in my mind. The friendly and knowledgeable adjectives were quickly replaced by seductive and sneaky. Developer evangelism in an extrapolation from the original term related to Christianity. It is about preaching, announcing, or otherwise communicating the value of a specific software product. Putting it this way, I can fully understand why it is considered important from the vendor’s perspective. But as with the original term, there is a thin line between praising something and proselytizing the audience. And the difference is in the eye of the beholder. A title can completely change the perception of a message or the intention of a conversation. A title always puts those in context. It is irrelevant whether the context is adequate. The interlocutor will most likely subconsciously adjust its boundaries, filters, and alert sensors accordingly. That is why I really don’t like the term, and I’ve always rejected to use that title myself. ## Developer Advocacy Masquerading the above under a not-so-obvious title? Yep, I hear that a lot. In fact, I used to think that myself years ago. Perhaps that was the intention when it was introduced. I have no idea how it came to be, but I can picture a brainstorming session aiming to replace "evangelism" with something embracing praising but free from proselytizing accusations. Once freed from the negative connotation, though, the term has evolved somewhat surprisingly. It has a double meaning which ironically makes it more accurate. To describe those, I like to use two of the definitions of the word "advocate" from the Merriam-Webster dictionary: one who defends or maintains a cause or proposal : That is the outbound direction. The modern successor of evangelism. Advocates build a case of the product and defend it. They don't expect developers to change their preferences and habits. They demonstrate how to achieve better results. They provide appealing evidence that supports the case. They understand well the strengths and weaknesses of the thing they defend and its applicability in different scenarios. Building trust and maintaining credibility are crucial for this role. : That is the part of the role that management, marketing, and sales appreciate as it can be indirectly connected to revenue generation. one who pleads the cause of another : That is the inbound direction. The acknowledgment that the community gathered around the product has a say. Advocates plead the cause of the community to the respective product management and engineering teams. They discuss various issues, ideas, and suggestions community members have. They try to understand their experience, expectations, and reasoning. They may pre-evaluate or even prototype some of them. Having strong technical expertise is crucial for this role. : That is the part of the role that product management, engineering, and advocates themselves appreciate most as it contributes to better products. ## Community > 'When I use a word,' Humpty Dumpty said in rather a scornful tone, 'it means just what I choose it to mean — neither more nor less.' Community is an awfully overloaded word. You can pick and choose a meaning that suits you. I like to think about it as the outcome of a social categorization and grouping process. It's well described by the social identity theory developed by Henri Tajfel and John Turner back in the 1970s and the 1980s. It represents the "us" part of the "us != them" equation. Note that the "them" part is equally important - there is no "us" *(in-group)* without "them" *(out-group)*. There must be something shared by all members but not shared by others. Belonging to a community is a matter of free will and acceptance. One must first self-identify as a member and then be accepted *(in the sense of not being rejected)* by other members. Abandoning a community is a matter of free will alone. No one owns the community. No person or organization can singlehandedly build a community. Some members provide more incentives or amenities than others, but that does not make them owners or managers. ### "Managing" a community While developer communities are a natural phenomenon, many software vendors started to see huge potential benefits in claiming and owning one. Yes, one. They realized they could use **our community** like some sort of a loyalty program! So they started to invest in an infrastructure that provides all the crucial things a community needs to function. And then, they appointed a dedicated community manager. Usually someone from the marketing team. To be honest, I don't know what native English speaker pictures when they hear the word "manager". Perhaps just a person who manages various things. But in the other two languages I speak, the word clearly appoints a role in the organization's reporting chart. My experience shows that sooner or later, executives expect community managers to treat the community in one of the following ways: as part of the organization : A low-cost work force. Or a department of volunteers, if you will. It's that mindset behind every "Let's ask the community to do this for us!" request. as a waiting room for future customers : A group of potential customers who are not yet ready to buy. It's that mindset behind every "We need to highlight the value of our paid products/services!" discussion. An interesting observation regarding the above perspectives is how the line between the in-group and out-group shifts from "the things we have in common" to "the benefits we can have". But the benefits are not the same for everyone. So when such an attitude becomes apparent (trust me, it always does), the natural social categorization and grouping are tampered, and the community starts to self-dissolve. Ironically, the only way to "manage" a community is to resist the temptation to do so. I personally like the term "fostering a community". But since I've never heard of a "Community Fosterer" role or job title, I'll keep using the widely accepted term throughout this post. ## Developer Relations Years ago, the Community Manager left the company I worked for at the time. Being unable to find a new one, the executives called a "how to manage our community" meeting. As one of the informal Developer Advocates, I was invited to attend. We came up with what we called CAT (Community Action Team). The idea was to have a formal body responsible for managing and growing the community. To ensure everyone's interest is equally represented, the body would consist of people from the company and the community. I was asked to bring the idea to life, and I started working on it. Shortly after that, I went on vacation, and when I came back, I was surprised to learn that CAT is no longer a thing, we have a newly formed DevRel team, and I'm part of it. Now, if you think defining community is hard, try finding a proper definition for DevRel. I've been searching for a description that fits my experience, and I couldn't find one. After thinking about it a lot, I decided it's probably best, to put it bluntly. It's that box in the organization's chart where Developer Advocates and Community Managers belong. Often, it is as simple as that - formalize a department within the organization. From the executives' perspective, that makes a lot of sense. Now that there is a formal unit, it is possible to assign tasks, set targets, and apply metrics. DevRel is an idea based on the same combine-two-skillsets principle that DevOps was born from. In theory, experienced developers with good social skills and a basic understanding of marketing should be good Developer Advocates. While marketing/PR folks with solid technical backgrounds should check up as Community Managers. In practice, things vary a lot. I've seen a DevRel being a re-branded marketing department. I've seen a DevRel label stuck to a group of software engineers who have to write blogs and speak at events in addition to what they usually do. And I've seen well-thought-out DevRel teams that really rock. There is another way in which DevRel shares DevOps' destiny. Some notable consulting agencies seem to push hard on the idea that establishing a DevRel team will bring a company to the next level, increase adoption, strengthen brand loyalty, improve the free to paid conversion ratio, etc. They conveniently don't attach a time frame to those claims, letting executives put unrealistically high expectations on their DevRel teams. One of the critical factors when planning a DevRel team is the target audience of the products or services. If there are no developers involved, there is, technically speaking, no need for Developer Advocates. There still may be a need for fostering communities, though. If there are developers involved, the question is to what extent? Some software products *(like APIs, frameworks, servers, etc.)* are meant for developers. Others *(like ERP software, online platforms, games, etc.)* are intended to serve the general public but may have areas *(for connectors, extensions, plug-ins, customizations, etc.)* that involve developers. That ratio should determine the team's structure, size, and priorities. The way I see it, there are three possible scenarios: Community team : The best approach when the product is targeted solely towards end-users. Foster a community of happy users that can help each other and spread the word about your awesome offer. DevRel as part of the Community team : Applicable when the developer-related surface is minor. Similarly, the main priority is to foster a happy and supportive community of end-users. But there is added value in attracting and supporting 3rd party developers, partners, extensions, etc. The structure makes sense because everything the DevRel team does contributes to the overall community value in this case. Community as part of the DevRel team : The way to go when the company's offering is primarily targeted at software developers. The focus, in this case, is 100% on ensuring the greatest possible developer experience. Developer Advocates play a crucial part here, fully utilizing the inbound and outbound channels discussed earlier. Naturally, software developers form a community around the product or service. Thus having community programs, recognition, awards, etc., is a nice add-on. Keeping an eye on and fostering other communities *(around languages, concepts, techniques, etc.)* helps understand developers' needs and habits better. But overall all community fostering activities contribute to providing excellent developer experience. ## Developer eXperience And so we have arrived at the newest kid on the block - Developer eXperience. It is such a hot topic that sometimes the number of DX experts among my social media contacts is growing with every hour. I'm not one of them, but Zeno Rocha, a former colleague of mine and *(at the time of writing this)* VP of Developer Experience at WorkOS is. This is how he defines it: > DX is the sum of all micro-interactions that a software engineer has with an API, framework, library, or internal tool. It's hard to argue with that statement, although I think there is more to it. Like the not-so-micro interactions with the people behind the product or other community members. Or the ability to find demos and samples for various use cases. Or the size and condition of the given product's ecosystem. There is probably a lot more. But when you put it that way, it brings the question, isn't DevRel supposed to provide the much-desired awesome user experience? Well, I think the answer is "not really" for at least two reasons. First, Developer Advocates *(or anyone by themselves for that matter)* can not **provide** an experience. They can contribute to it, but the overall experience is a result of the work of many people. Even the best of us can not compensate for poorly written code, lacking features, overcomplicated APIs, business decisions restricting access, broken processes, etc. At least not without introducing extra abstraction layers or bending the rules. In that context, having a DX team indicates that developers' experience is essential for the company. It's is not the role of the DX team to provide it. It is to ensure everyone involved in the process takes it seriously. It's about spotting developers' frustrations, identifying what is causing them, and working with other teams to remove or minimize them. Second, a vast area significantly impacting the developers' experience is largely neglected by DevRel teams - namely Developer Education. I'm OK with stating that because blogs, conference talks, demos, etc., are not educational activities as far as I'm concerned. I am aware many DevRel teams have technical writers responsible for product documentation. Some larger organizations have dedicated teams working on courses, training, and even books. I'm not suggesting restructuring those but rather that the learning experience is part of the developer experience. Whoever is responsible for designing and implementing the learning experience must consider it in the broader scope of Developer eXperience. ## Developer Education To my surprise, there is not a lot of buzz around developer education. It's almost like everyone takes it for granted. Meanwhile, it is a complex area requiring quite some expertise. I'm by far not an expert in that field, but I'll share with you some of the key factors to take into account. Starting point *(prior knowledge and experience)* : Whatever the assumption is, it is wrong. Not everyone needs "Hello world", but not everyone knows the "basic concepts". Not everyone knows the frameworks and tools required, but no one wants to scroll over things they already know to find the significant bits. The best approach is not to assume. Observing what people search for and can't find, what they skip, complain about, etc. can help build targeted learning paths. Learning material formats : Some people prefer to read. Other to watch videos or listen. Some can not learn without interactive exercises and having a teacher/trainer checking their work. The formats also come with varying attention spans that one needs to consider. There is no one format fits them all approach. Outdated vs. relevant knowledge : The only constant thing is change. Products evolve, and knowledge important yesterday can be obsolete and misleading today. On the other hand, people don't always catch up with the product. Some may still need to learn about an earlier version of the product. The more mature the product is, the more complex it becomes the manage the education effort. Knowledge assessment and certification : It may seem that creating a test or a quiz is easy. But doing it in a way that reliably indicates something is very hard. There is a reason why certification authorities exist. Whether or not such authority needs to be involved in the knowledge assessment process depends on many factors. Either way, the quality of the process (or the lack of one) impacts the learning experience. The above is far from an exhaustive list. But hopefully, it is enough to illustrate how developer education can improve or ruin the developer experience. ## Summary Developer Evangelism and Evangelists : are things from the past. While some of those units and titles are still around, I expect them to migrate to advocacy sooner or later. Developer Advocacy and Advocates : are the industry-wide accepted terms. Those are the folks who promote and defend products and ideas but also plead the cause of the 3rd party developers. The double meaning of the word "advocate" nicely highlights the fact it's a two-way street. Community Management and Managers : are here to stay. The idea of managing people without having any formal relationship with them is an awkward one if you ask me. But no one else seems to be bothered by that. In reality, those folks probably do not manage the community itself but rather the tools, processes, and programs that improve operations and interactions. Developer Relations : is the industry-wide accepted term for a given part of an organization where the roles mentioned above belong. It allows grouping, coordinating, and measuring the results of all advocacy and community-related activities. Developer Education : is focused on providing the best learning experience to 3rd party developers. The primary goal is to flatten the learning curve as much as possible for as many people as possible. Developer eXperience : is kind of an umbrella term for all of the above. It groups all specific goals into one uber goal - ensure developers have the best possible experience when interacting with the product or its surroundings. My next post, titled "[From DevRel to Developer eXperience](https://MilenDyankov.com/blog/2022/02/from_devrel_to_developer_experience)" is about how those views shaped the structure of an actual team in the company I work for. ## Why dev heck? I've spent quite some time clarifying and organizing my thoughts on all those topics during the last few weeks. For what it's worth, I thought I'd share them publicly. I hope you found them helpful or at least interesting. As always, your feedback is more than welcome. Whatever your take on DevRel, DX, community building, or any of the related matters, I hope you'll have many satisfied users and excited developers. # From DevRel to Developer eXperience At the time of writing this, the DevRel team at [AxonIQ](https://axoniq.io){rel=""nofollow""} consists of four people (including me). We are two Developer Advocates, a Community Platforms Developer, and a Learning Experience Designer. The flat structure makes perfect sense at this team size. But our plans are huge, and four people can only cover so much ground. Clearly, the team needs to grow. Instead of just rushing to hire a few more folks, I started asking myself how the team would look like in, say, two years from now? What roles and responsibilities are crucial and aligned with the company's growth? How should those be organized? I like to have a plan. Even if only to change it later on. So here is one. And I'm going public with it. Perhaps it may help other folks facing the same challenge. ## Understanding the domain As with any project, I needed to fully understand the domain space I'm trying to model. I spent some time researching and thinking about it. The "[What dev heck?](https://MilenDyankov.com/blog/2022/02/what_dev_heck)" post I published earlier covers my findings and thoughts. Long story short, two things became apparent: Developer eXperience is the main domain : If you have read my previous post, it should be obvious why that is. The consumers of the products and services AxonIQ provides are software developers. It is crucial to ensure they have the best possible experience. Our core principle, "We put quality first, always." must also be applied to the interactions and processes developers go through. Developer Relations, Developer Education and Community, are the sub-domains : While they all are crucial parts of the developer experience, each of those areas requires slightly different skills and mindsets. Organizing it this way helps understand the scopes and the relations between them. The diagram below may look like a corporate ladder, but it is not. It is merely marking the boundaries of the sub-domains. ## The DX domain The main Developer eXperience domain has one aggregate - the "VP of Developer Experience" (Yay! Corporate ubiquitous language 😉). Its primary responsibilities are around strategic planning. The DevRel and Developer Education sub-domains have a lot in common, but no one can contain the other. Community is part of DevRel; as in our case, it fosters developer communities. The one around our products but also many related ones. Here is what it looks like: ![DX structure](https://MilenDyankov.com/assets/2022-02-10-from_devrel_to_developer_experience/dx.png) ## The DevRel sub-domain Obviously, the most critical role in that scope is the "Developer Advocate (Java)" one. In my previous post, I have dived a bit deeper into [what developer advocacy is](https://MilenDyankov.com/blog/2022/02/what_dev_heck#developer-advocacy). At the time of writing this post, AxonIQ's offer serves best developers using Java or JVM-based languages; thus, the Java focus. As we evolve our support for other environments (like .Net for example), we'll adjust the team accordingly. And of course, it is much better to have a relationship with people who speak your language and understand the local culture. Thus, we would love to have advocates in North America, DACH, and UK/Ireland regions (for a start). The "Head of Developer Relations" is the same as "Developer Advocate" one plus the responsibility to plan and coordinate the activities of the whole team. ![DevRel structure](https://MilenDyankov.com/assets/2022-02-10-from_devrel_to_developer_experience/devrel.png) ## The Community sub-domain The primary goal of the community team is to facilitate communication for 3rd party developers. Both among themselves and with the AxonIQ folks. Two of the main tools in that space are the [Discuss platform](https://discuss.axoniq.io/){rel=""nofollow""} and a dedicated developer portal (still work in progress at the time of writing). The responsibility to keep those (and future ones) helpful and friendly lies in the "Community Platforms Developer" role. Naturally, we provide the most incentives and amenities in the community around our products. But we understand all too well we don't exist in a void. There are plenty of other communities and organizations our users belong to. We'd love to contribute to and partner with those to better understand the various contexts developers operate in. That is the scope of the "Communities coordinator" role. Last but not least, we really like to show our appreciation to all the folks who help us. There are so many ways to contribute and support us. All valuable and much appreciated. The "Head of Community" role focuses on building and executing recognition and award programs. It also constantly monitors the community's health and growth. ![DevRel structure](https://MilenDyankov.com/assets/2022-02-10-from_devrel_to_developer_experience/community.png) ## The Developer Education sub-domain We have been investing a lot in training software developers. I don't have the exact number of all trainees to date, but it's in thousands. We also created [AxonIQ Academy](https://academy.axoniq.io/){rel=""nofollow""} to bring the learning experience to the next level. To date, we have \~1300 learners there. We know from experience education is a big part of the developer experience. Ensuring top-notch education programs is the "Learning Experience Designer" role's ultimate responsibility. While the main focus is, of course, on the academy, it does not stop there. Everything, from reference documentation to tutorials to complete classes, falls in that scope. I hope the "Technical Writer" and "Trainer" roles are self-explanatory. The only caveat is that we would like the trainers to share part of the responsibilities of the solution engineers (in other words, be a part-time solution engineers). Our learners appreciate practical examples and lessons learned from the battlefield. There is nothing like a trainer who can provide real-life context to a theoretical study. ![DevRel structure](https://MilenDyankov.com/assets/2022-02-10-from_devrel_to_developer_experience/education.png) ## What's next? Well, grow the team 😉. With [that plan](https://www.mindomo.com/mindmap/developer-experience-team-a624d1da224d4489b88bc61f8bc21270){rel=""nofollow""} at hand, we are well prepared to search for and find the right people. Or so I hope. By the way, if you can imagine a picture of yourself replacing one of those people icons, do not hesitate to drop me a line at milen.dyankov\[at]axoniq.io. ## Why am I sharing this? Two reasons: - it may serve as food for thought for other people building or growing DX or DevRel teams. If that's what brought you here, please treat the above as inspiration, not a receipt. Consider the offering and the specifics of your organization. - It may actually help me find the right people. There is no way I could possibly explain all this in a job offer. My experience shows candidates have all kinds of different visions and expectations about the DX, DevRel, etc. Being transparent about it allows people to judge how our vision matches theirs. I do hope that will make the recruitment process a bit more efficient. # The macroscale of micro frustrations I have experience with two GPS navigation systems. My Android phone has Google Maps. My car also has one, powered by TomTom. All things being equal (as when I drive my car) both are in direct competition. I have to put my trust in one of them. I can't objectively tell which one is better. Yet, somehow I tend to use one of them often and avoid the other. In this case, my decision-making process seems as irrational as the ones software developers have while adopting frameworks and tools. So I got intrigued and tried to debug and backtrace it. The more I think about it, the more I believe both are influenced by the same perception of reality. ## The facts Google Maps navigation got me in trouble twice. Both were attempts to avoid a traffic jam. Several years back, it navigated me through a road that didn't actually exist. It happened in Romania. The "road" turned out to be a narrow path in the middle of a cornfield. The second time was last summer in Poland. The bridge that was supposed to bring me back to the main road after a 40km detour didn't exist either. I have never had the same issue with the TomTom navigation in my car. In fact, I can't recall a single time it was significantly wrong about a road. But man, it's so frustrating. It changes directions without asking or even notifying. Sometimes it does so while I'm in the middle of an intersection. Several times it made me "avoid" a traffic jam, only to return after the exact same vehicle I had in front of me before. It tells me how long to the next gas station, but not if it's on my side of the highway. It directed me on a paid road that requires an e-toll account. I had to pull over, call the highway service, and ask how to pay the highway fee to avoid a significant fine. ## The perception The two major issues I had with Google Maps didn't ruin my trust in it. I can understand how mistakes like those can happen. I almost don't remember them. I only recall them occasionally as a fun story. Also, those were several years apart. They seem to be the exceptions of otherwise well-functioning software. On the other hand, the TomTom system in my car causes micro frustrations almost every time I use it. None of them has the severe consequences of a missing road or bridge. But those pile up quickly. Those micro frustrations build up the feeling of a fragile, not well thought, underdeveloped, etc., product. I know that is not the case. I have good friends at TomTom (I was once considering joining their team in Poland) and I know many super-smart folks work there. I admire the things they do. ## The difference The fundamental difference is that while TomTom is great with maps, Google is great with user experiences and not terrible with maps. An OK product providing a great user experience will almost always be more appreciated than a great product having an OK user experience. The same rule seems to apply when the users are software developers. Just because they understand the value of a framework or API and appreciate the effort put in building it does not mean they will survive ever piling micro frustrations. In the last decades, I've observed some great technologies (like Portlets and OSGi to mention a couple) that ignored the micro frustrations factor for way too long. They gained adoption while there was nothing comparable feature-wise. Eventually, they lost the battle with not-so-good but less frustrating alternatives. The adoption (and thus the potential commercial success) of a developer-focused product is a function of product quality and the developer experience it provides. # Operational vs. Strategic DevX It is 2022, and Developer Relations (DevRel) and Developer eXperience (DevX) teams are to be found in almost any organization whose offering has anything to do *(even if very little)* with software developers. Many prominent examples prove that DevX-done-well significantly contributes to the organization's success. Way more organizations hope to replicate the success story by merely "sprinkling a DevX spice" on top of their traditional marketing/sales-driven practices. There is nothing wrong with that per se. A better developer experience doesn't hurt anyone. But building it sometimes does. It may be challenging, confusing, or even frustrating for DevX professionals to constantly confront their goals with sales and marketing objectives that do not account for the nature of the target audience. Here are some hints on how to approach the situation if you are one of those professionals. ## Which DevX Is Which There are many ways to slice and dice the DevX problem space and group the individual activities. But concerning how it fits into the organization's processes and practices, distinguishing strategic from operational activities is likely the most helpful way of thinking about it. Operational DevX consists of the day-to-day tasks contributing to the overall developer experience. Within the Developer Education space, those would include improving the documentation, building courses and training, utilizing modern channels and platforms, etc. Within the Developer Relations space, those would include handling feedback, prototyping, contributing to the product, organizing/attending/speaking at meet-ups/events, writing blogs, etc. Within the community management space, things like managing collaboration platforms and various ad-hoc activities to encourage participation and show appreciation are all operational. None of these alone is of strategic value. All are important, but usually, one element can compensate for the lack of or poor quality of another. The bottom line is operational DevX is not in a position to contribute to achieving strategic, long-term business development goals. It treats the developer experience as a nice-to-have added value rather than a must-have to be successful. Strategic DevX is about making the developer experience a central part of the organization's business strategy. It's about understanding the developers' journey toward the product, their decision-making process, the epiphany, satisfaction, disappointment moments, etc. Such understanding allows the organization to judge which activities make sense at which stage. It allows for building a behavioral framework with dos and don'ts that every individual representing the organization should follow. It makes it possible to design the interaction with the community members in a non-aggressive, developer-friendly fashion and then capitalize on their satisfaction. ## Which DevX Should You Do If you are a DevX professional, that decision is not yours. It's the organization's senior leadership that gets to make it. Consciously or subconsciously. First of all, strategic DevX doesn't always make sense. It largely depends on what role developers play concerning the product. For example, companies providing streaming content online *(music, movies, etc.)* often have APIs to allow external developers to create enhancements and new features. Those companies foster developer communities around those APIs and care much about DevX. But ultimately, they sell services to consumers and can do that with or without external developers. Developers are not the consumers, and their satisfaction levels do not determine the company's success. So it makes no sense to position DevX strategically. The counter-example is companies selling IDEs, software development tooling, frameworks, libraries, etc., where the developers' satisfaction is a substantial purchasing decision factor. Where it makes sense, strategic DevX is almost always a conscious decision. That is so because, in practical terms, it requires careful alignment of the individual targets of different departments so they don't collide with each other. Everyone must commit to the long-term goal and accept the necessary operational trade-offs. In abstract terms, it changes the "sell to developers game" from finite to infinite. > "A finite game is played for the purpose of winning, an infinite game for the purpose of continuing the play." > > \-- James P. Carse, Finite and Infinite Games: A Vision of Life as Play and Possibility From the above, it is safe to conclude that if your senior management expects you to do a strategic DevX, you are most likely well aware. Thus, the apparent counter-conclusion is that if you are not sure, they expect you to do operational DevX. Well, it's not always that simple. Often leaders would subconsciously expect the long-term results of a strategic DevX to be achieved with purely operational DevX complying with a strategy unilaterally defined by other teams *(most notably sales or marketing)*. At the same time, they may say things like "DevX / DevRel is crucial for us". The more you hear such motivational statements, the more it strengthens your belief that you are expected to do strategic DevX. But making such a false assumption may rapidly bring you down to the land of conflicts and frustrations. The apparent solution - "just ask management" - doesn't always work. You may not have a direct line to the right people. They may not be aware of the distinction and not have the time *(or the patience)* for you to "educate" them. They may think confirming your assumption is a harmless act that will comfort and motivate you. Luckily there are ways to determine this yourself by just examining the environment. The first hint is to look for where DevX is located in the organization chart. If it reports to the senior leadership, typically, the expectation is to do strategic DevX. In such a case, you should have no doubts as the intention should have been clearly communicated and discussed. If it wasn't, your organization might be simply copycatting the structure of another successful company. If DevX is part of the Marketing, you are almost certainly expected to be operational and conform to the marketing strategy. It is the same if it is part of Engineering, except the focus is likely more on education or encouraging contributions. If DevX is part of the Product team, it can be either. The expectation largely depends on how the product team itself is positioned. If it is under Sales, then most often than not, DevX is nothing more than a fancy-sounding alias adopted by sales folks. The second hint is how strategic decisions within the organization are made. This can be tricky and requires a good understanding of what is strategic and what is not from an organization's perspective. Typical strategic decisions are those about what features a product should offer, how it is licensed, what is provided for free, what is the pricing model, sales objectives and goals, target groups, product branding, company branding, etc. Of course, those vary significantly from company to company, but that doesn't change the pattern. The DevX is either included in the strategic decision-making process or not. If it is not, and you only learn about those decisions after the fact, you are almost certainly expected to only do operational DevX. The third hint is even trickier as it may fool you. It is how DevX's work is measured. If, within a given period, you are expected to produce X blog posts, speak at Y events, or respond to Z messages on the forum - that's clearly operational DevX. However, it may be that the measured objectives are the number of downloads / API calls, the community's new vs. inactive members ratio, users' sentiment analysis, etc. Of course, satisfying results in those areas are only possible when doing strategic DevX. As I mentioned, senior leadership wanting the result does not necessarily mean they place DevX strategically within the organization. ## Which DevX Do You Want To Do Knowing what the expectations are is one thing. How one feels about them is a whole different story. Much like organizations, people have their own goals and ambitions. When those are aligned with the expectations, the DevX team is usually a friendly, excited, and self-motivated bunch of folks. Long-term misalignment leads to boredom, annoyance, conflicts, frustrations, and eventually burnout. If you are only good at operational DevX but expected to do strategic one, do yourself a favor and don't play by the "fake it till you make it" rule. Strategic DevX requires a fair share of software development, product management, operations, marketing, and sales knowledge, accompanied by somewhat advanced soft skills. Most of those come from experience and are next to impossible to fake. So let someone more experienced do it. If you want to grow into the role, work closely with that person. Constantly ask why, listen to arguments, question them, research, draw own conclusions, verify them, come up with what-ifs, etc. As with any profession, it takes time to master the craft. The situation is a bit more complex for strategic DevX folks expected to do only operational stuff. Assuming one is competent and experienced in the field *(not just hunting for a fancy title, higher position, or a pay grade)*, that is not a sustainable arrangement. If that's your case, you need to consider several factors. First, does strategic DevX makes sense for the organization? If not, then well, you have your answer. You'll never be able to do what you are good at within that company. Then *(assuming yes to the answer above)*, do senior leadership recognizes the value strategic DevX can bring? If they don't, this is your opportunity to shine with your people-convincing skills. If they do but are still hesitant, you need to understand why. Is it a lack of trust in your skills or concerns about a collision with other people's goals *(rendering annoyance and frustrations inevitable)* or perhaps a belief that the same results can be achieved differently? Whatever the case, it'll be a significant effort and a long period of attempts to prove your point. And there is no guarantee you'll succeed. Meanwhile, you'll still be doing somewhat "boring" operational stuff. So should you bother? That depends greatly on your belief in the product or company's (the people) mission. Based on that, you should be able to establish a time frame within which one should manage to change another's mind. Either you prove your point, and DevX becomes strategic, or you decide that you can better contribute to the success in another way. If none of this happens, then you need to go different ways. But at least you'll do so knowing you've tried your best to make things work. # Multi-Hat Disorder - Building Personal Website Like an Enterprise App While I’ve been focused on helping companies grow, my personal brand has taken a hit. I’ve never been interested in being an influencer or a showman, but I’ve come to realize that technical expertise alone isn’t enough anymore. It seems that, in today's world, a "personal brand" carries as much, if not more, weight than the substance itself. It's been a smooth journey for me over the last two decades. Each new professional opportunity has resulted from someone knowing and appreciating what I’ve achieved in my past roles and engagements. I haven’t had to promote myself; it was the word-of-mouth regarding my knowledge and experience that spoke for me. As much as I’d like to believe that is still the case, it would be naive and irresponsible to ignore the changes in the software development landscape over recent years. I took a hard look at my personal website (last redesigned in 2016) and had to admit that I wouldn’t intuitively trust the person it portrays for today’s projects. So, I sat down to revamp it. ## Wearing All the Hats The great thing about building a personal website is that I get to wear multiple hats. I’m the *client*, the *architect*, the *developer*, and the *copywriter*. It's like a proof of concept for a real project, where the toughest negotiations are with myself. I tend to have strong opinions when playing each of those roles, so the most challenging part was letting go and finding agreement on tradeoffs. Now that I've done the exercise, I'm sharing my thought process. Keep in mind **this isn't a blueprint, a set of best practices, or a pattern to follow**. It's merely food for thought. The irony, sarcasm, and exaggerations are intentional. ## Defining the Goals A project needs clear goals. If I were to write a formal RFP for it, these points would likely be on it: ```markdown Objectives - Show strong professional profile and industry positioning - Present clear value proposition - Establish credibility and seniority Target Audience - Companies looking for consultants / subcontractors - Engineering managers, CTOs, PMs - Architects, team leaders, and developers learning new concepts - Conference and tech event organizers Requirements - Fast, accessible, and SEO-friendly site - Content editable without developer involvement - Mobile-first, responsive layout - Minimal to none hosting costs - Crawlable without JavaScript ``` Obviously this is just a very small subset but sufficient to illustrate **why** the software is needed and **what** outcome is expected. No project, not even a personal website one, should move forward without answering those questions first. ::hint If you want to build an RFP-style list like the one above, I strongly recommend chatting with your favorite AI assistant. Don't expect a perfect result immediately, though. Have a conversation, iterate, evaluate, and ask "why". Tell it to remove or change things that don't fit your actual needs. When you think you're done, ask "Is there something I'm missing?". If privacy is a concern, use private or local LLMs. Many companies run their own inference servers with [vLLM](https://vllm.ai/){rel=""nofollow""} or similar runtimes. If your machine has the power, consider something like [Ollama](https://ollama.com/){rel=""nofollow""}, [LM Studio](https://lmstudio.ai/){rel=""nofollow""}, [LocalLLM](https://localllm.com/){rel=""nofollow""}, etc. Even relatively small LLMs these days are being trained on enough requirements documents to provide useful suggestions. :: ## Negotiating the Tradeoffs ### Quick and Easy vs. Maintainable and Simple Just vibe-code it – that's what the *client* in me says. Time to market is essential, the value is in the content, not the code, and there are other important things to work on. Those are reasonable and strong arguments. In fact, they initially felt so persuasive that I initially did vibe-code it. I used [GitHub Copilot](https://github.com/features/copilot){rel=""nofollow""}. The free subscription was just enough to allow me to use its **plan mode** to build a spec and then implement it. The plan wasn't as detailed as those I'm used to from working with [Cursor](https://cursor.com/){rel=""nofollow""}, but it wasn't bad. If I hadn't had to save credits for the implementation, I probably would have gone through a few more iterations to polish it, though. The *architect* was cringing inside, but couldn't deny there was a website that was technically working and arguably meeting most of the requirements. The *developer* felt he wasn’t involved in the project at all. If the *client* was to accept the result and declare the project complete, it would have been yet another amazing vibe-code success story. Sadly, the *copywriter* reported some issues, the acceptance tests revealed performance and compliance problems, and the SEO-as-a-service provider was very skeptical about the anticipated outreach. As a result, the client requested **a few minor changes**: ```markdown - Ensure straightforward, unified navigation across devices. - Allow commenting on and rating of individual content (e.g., blog posts). - Automatically optimize assets (images, videos, and so on). - Automatically generate meta tags and Open Graph data from content. - Ensure compliance with relevant privacy regulations. - Make it machine-readable and LLM-friendly. ``` In all honesty, I didn't even try throwing those details into an AI prompt hoping it would magically fix the issues and implement the changes correctly. Some would argue it could have worked. I argue that even if it did, it would likely result in more "minor changes." My experience with creating such "infinite loops" in the past suggests the cost (time and tokens) would significantly exceed the project's budget. Instead, I forced the *client* and the *architect* to have a serious conversation about tradeoffs. Clearly, the expectation is that the product would have to evolve and adapt to external forces. From a software architecture standpoint, that rules out a black box that only some AI "knows" how to deal with. From a business value perspective, manually typing the code in is suboptimal, to put it mildly. The compromise we reached can roughly be summarized as "**abstraction level LLM scoping with a context exchange loop**": - The *client* uses an LLM to summarize the business context and evaluate scope changes and new requirements against an architectural context. - The *architect* designs the solution with simplicity and maintainability as the primary technical goals. An LLM validates the architecture against the business context and produces an architectural one. - The *architect* selects a tech stack that matches the knowledge and experience of the *developer*. - The *developer* uses an AI coding assistant to quickly implement specific tasks, given the architectural context, but always verifies the generated code and takes full accountability for its quality. As the famous saying goes, "Context is king." The tricky part is that every changed or added requirement, or implemented decision, changes the environment, which in turn changes the context. ### Build vs. Buy An off-the-shelf solution that met the requirements without imposing a learning curve on the *copywriter* would have rendered the project unnecessary. But the *client* couldn't find an affordable one. On the other hand, developing everything in-house is an amazing opportunity for boosting the self-esteem of the *architect* and the *developer*. Which sadly carries risks and enlarges the accidental complexity surface. Take for example the content rating and commenting feature. Technically, it implies managing user accounts and permissions, auditing capabilities, spam protection, etc. Legally, it makes me a Data Controller in GDPR terms. Similarly, collecting information for analytics and improving SEO requires asking users for consent and respecting their choices. If you (like me some time ago) think that a simple "we use cookies" banner solves it, search for "consent mode v2". The tradeoff here was to swallow the creator's pride (the *developer*), sacrifice some nice-to-have features (the *client*), and account for additional integration logic (the *architect*), in exchange for risk delegation and simplicity. And so, now [Disqus](https://disqus.com/){rel=""nofollow""} handles the commenting and rating, [CookieYes](https://www.cookieyes.com/){rel=""nofollow""} handles the consent management. The integration, where possible happens through [Google Tag Manager](https://www.google.com/){rel=""nofollow""} *(yes, there is a performance related tradeoff here too)*. ### Most Popular vs. My Favorite While there’s no vendor lock-in risk *per se*, as a *client* I must consider the hypothetical possibility of transitioning the product to another development team. From that perspective, selecting a widely adopted and popular technology stack is generally preferred. In practical terms (at the time of this writing), that almost invariably points to the `React` ecosystem. It's a mystery to me why so many developers have embraced that somewhat peculiar mix of HTML within JavaScript, but it appears to be the most popular choice. Consequently, this is the stack AI coding assistants are most familiar with. Me, the *developer*, immediately saw the perfect opportunity to demonstrate Java’s continued relevance. After all, I’ve built countless enterprise systems with it over the past 20+ years. Of course, no JSP or JSF; Vaadin is the way to go. It may not have the ecosystem of popular JavaScript frameworks, but it comes surprisingly close. And if that’s still not enough, the fact that this isn’t a mission-critical project makes it ideal for experimenting with pure HTML+CSS solutions like HTMX and DaisyUI. A tough one for the *architect*. Java is rock solid but likely overkill for this project. Furthermore, it never ceases to amaze me how few affordable options there are to host Java web applications (why oh why there is no Vercel for Java?). TypeScript seems a reasonable compromise between the expectation of wide adoption and my experience with strongly typed, well-organized languages. And then it's time to open the Pandora's box of UI frameworks. React simply doesn't fit well into the backend developer's mind, even with the help of AI. Svelte is tempting, due to its compiler and generally minimalistic approach, but it feels it’s still a niche rather than mainstream. The risk of Angular forgetting about backward compatibility and turning everything upside down again is something I'd rather avoid. So Vue.js it is. ### Web Application vs. Static Pages The dilemma here stems from non-obvious contradictions in requirements or previous architectural decisions. Before delegating the rating and commenting features to external providers, a web application would likely have been the most logical choice. With those concerns offloaded, static pages seem feasible and tempting. But then there’s the requirement regarding optimizing assets: do it at runtime for maximum flexibility, or at build/deploy time for maximum stability? What about changing or adding content: query the state at runtime, or regenerate everything at build/deploy time? With my *architect* hat on, I want to defer that decision for as long as possible. A definitive call early in the process puts unnecessary constraints on many subsequent decisions. Ideally, I want to pick a tech stack that lets me, the *developer*, build in a way that’s decoupled from the deployment artifact. I've had good success with [Quasar](https://quasar.dev/){rel=""nofollow""} in the past, but it would be cumbersome if I chose the SSG road later on. Conversely, tools like [Astro](https://astro.build/){rel=""nofollow""} and [VitePress](https://vitepress.dev/){rel=""nofollow""} are great for SSG, but extending the out-of-the-box functionality can be challenging. So I decided to go with [Nuxt](https://nuxt.com/){rel=""nofollow""}, which supports SPA, SSR, and SSG modes and keeps my options open. I like it as a *developer*, too, because I can run in SSR mode locally and see the changes immediately in the browser, and only generate the whole static site when I'm done. The previous paragraph already reveals my eventual decision to go with a static site. This was largely motivated by a desire to eliminate nonessential infrastructure. It's not that there's anything inherently wrong with Vercel or Netlify—it's just that GitHub Pages works equally well for this use case. Of course, should I need to fully embrace static site generation in the future, that option remains open. ## Discovering a Multi-Hat Disorder Before you ask, I don’t have Multiple Personality Disorder. Not officially diagnosed, at least. But after writing this piece and reading it back, I feel I do have some sort of Multi-Hats Disorder. Someone please tell me if that needs treatment, and if so, recommend a specialist. Seriously though, I didn't really apply a full-blown enterprise app development process to produce a personal website. But I did spend a fair share of time thinking about what and how to do, and evaluating different options. At some point it struck me how similar my decision-making process was to the roles I’ve been in the past. Different domain, scale, importance, timeline—virtually everything—yet the fundamental concerns and tradeoffs are almost identical. So I started taking notes, hoping to come up with some pseudo-smart social media posts. It ended up being a pseudo-smart blog post. Oh well, I hope it was worth your time. # Finite and Infinite Startups Three weeks ago, Adam Wathan, the creator of Tailwind CSS, [released a podcast](https://x.com/adamwathan/status/2008909129591443925){rel=""nofollow""} containing an honest confession – "We had six months left" – and an explanation of why they had to let some folks go. For days, my social media feeds buzzed with discussions about the subject. I saw everything from "AI is killing startups" to "he doesn't know how to run a company," and even ridiculous accusations of deliberate exaggeration (why say 75% when it "only" affected three people). I never cease to be amazed by how many people fill the gaps left by scant information with assumptions and rush to judgment. ## What Kind of Game? I was a bit angry at AI too. I felt his pain and admired the courage to publicly admit mistakes. Part of me was thinking, "they should have seen it coming," while I genuinely hoped they’d survive it. But what really surprised me was that Tailwind wasn't behaving like most startups these days. I started listening to Adam's previous podcasts, and increasingly a thought formed in my mind. When the goal isn't to cash out at the highest possible valuation but to survive and continue your mission, you must play by different rules. > There are at least two kinds of games. One could be called finite; the other infinite. A finite game is played for the purpose of winning, an infinite game for the purpose of continuing the play. \- James P. Carse, Finite and Infinite Games, 2013 The above is the opening of one of my favorite books. In the following 150 pages, it walks the reader through the different rules each game kind is played by. I find it eye-opening and fascinating because everything, including running a startup, is a game. And I can tell from experience that there are at least two kinds of startups: those who play a finite game and those who play an infinite one. Just to be clear—this isn't about right, wrong, or better. It's about understanding which rules apply. Because, regardless of whether you're among the founders, employed by a company, contracting with them, or even just applying for a position, you're in the game. It took me a very long time to realize that. ## Finite Startups I'm tempted to write that most startups today are finite ones, but I lack actual data to support the claim. It certainly feels that way though. Startups frequently and happily publicize their funding rounds, and it seems there's almost a weekly announcement of some mind-blowing valuation, occasionally culminating in an acquisition. Internally, the metrics that seem to "make sense" tend to always be some form of growth indicator. Incentives are structured around stock options and other promised gains after a successful exit. A finite startup is one where the company itself is the product. Everything else—offered products, and services, ambitious plans, amazing innovations, impressive headcount—are simply means and tools to increase valuation. The game is played with a clear objective: to achieve a defined, though often unstated, win condition—typically, a sale above a specific ROI. The rules of the game are mostly fixed. While they don’t enforce particular behaviors, they restrain the freedom of the players within predefined boundaries. Knowing (or, more accurately, sensing) and accepting the finite game rules is crucial when working for such an organization. It's very easy to fall for the various high-flown, inspirational, and motivational slogans—the bright future, exceptional culture, unseen innovation, and so on. It's not that those things are lies, exactly; rather, they're part of the play. ## Infinite Startups In my experience, these kinds of companies are rare, but far more challenging and interesting. They’re established with the intention to last—not to win the game, but to ensure the game goes on and things get better. They require players with a survival attitude and a broad perspective, which is quite different from a narrow, score-driven strategy focused on winning a prize. Take today's prominent examples of success like Google, Apple, and Facebook for instance. They started as fairly unknown startups and staying in the game is what made them the big tech giants. One may argue it was easier to be an 'infinite player' in the past, when Venture Capital was less focused on chasing unicorns and more on investing in solving hard problems and building lasting businesses. I personally believe it's more a shift in societal mindset, fueled by the seemingly endless parade of success stories featuring young entrepreneurs and influencers. It’s not just the VCs, but also many founders (and even employees) who are now motivated by the prospect of a quick, substantial win. The reality, of course, is that playing a role in an infinite game has always been considerably harder and riskier. An infinite startup doesn’t play by a standard set of rules. When it enters the arena, some rules are already established by previous players. It introduces new products and services, and their adoption eventually shifts the landscape. Other players do the same. Consequently, new rules emerge. Therefore, it's next to impossible to predict what the rules of the game will be in the future. Ultimately, what matters most isn't the valuation, but the share startup's products and services have in shaping the future playing field. ## Why It Matters After all I've read (and listened) about Tailwind, I can absolutely classify them as an infinite startup. Their products are definitely shaping the web development field, and they'll likely survive and perhaps even emerge stronger, after losing one of the many finite games that every infinite game consists of. The way people misjudged Tailwind reminded me of the mistakes I've made in my own career. These go both ways; perhaps by sharing them, I can help you avoid the same friction. There was a time I mistook a finite game for an infinite one. I knew the company was an infinite startup even though I wasn't familiar with that concept at the time. They were self-funded and resistant to selling, despite significant interest from some of the biggest players in IT. Their ambition was to be able to address as many of the world’s inequities as they possibly could. I loved their product; it was ahead of its time, successfully competing with solutions from billion-dollar companies. To me, the product was the infinite game. I was naively expecting it to forever improve, evolve, and garner more developer love. Perhaps they won that finite game, perhaps not, but the landscape changed, the product became legacy tech, and most developers moved on. I wasn't able to reconcile myself with that, so I left. I have no regrets; I've always been more attracted by chasing tech innovation, but I have to admit to myself my misjudgment. The company is still around and doing well (as far as I can tell) in their infinite game. There was a time I let myself believe a finite startup was an infinite one. All the usual signs were there: a focus on short-term goals, an emphasis on conversions, the habit of calling everyone with an account a 'customer', and so on, but I ignored them. I guess deep down I knew what I was dealing with, but I desperately wanted to believe the message in those inspirational speeches was genuine. I wanted us to indeed be on a mission to shape the future, change mindsets, and improve things for all. So I kept pushing for products and services that could have a bigger role in shaping the future. But because those efforts didn't contribute to higher valuation, it only made me drown in frustration. A frustration born from a lack of appreciation for my hard work intended to ensure the bright future that, ultimately, no one was interested in. The misalignment of goals eventually led to our parting ways. I didn't intend this to be that long when I started writing it. Yet, I feel there's still so much unsaid. But I'll end it here. If you haven't read *Finite and Infinite Games*, I highly recommend it. Either way, I hope I've prevented you from making some of my mistakes. Now go make your own ;) # AI for Application Developers > You don't have what it takes to be a programmer! That is what my high school informatics *(that's what it was called at the time)* teacher told me. She wasn't entirely wrong. In the 90s, being a programmer often required a strong aptitude for mathematics—something I clearly lacked. Back then, if you needed a list sorted, you had to implement the algorithm yourself. We were "tortured" with QuickSort and MergeSort in class; virtually everything we did with computers was rooted in manual math and low-level algorithms. Fast-forward thirty years. Today, millions of software developers build amazing solutions without ever touching trigonometry, linear algebra, or stochastic calculus. While algorithms remain essential, we operate at a significantly higher level of abstraction. For lack of a better term, I call this group Application Developers. We are the people who design and build the libraries, tools, and end-user applications that run the world. I've occupied that space throughout my career, from junior developer to technical leadership. ### The Missing Middle Ground In my observation, application developers are struggling the most as the AI revolution unfolds. The landscape is heavily polarized. On one extreme, you have the scientific perspective: academic papers bloated with linear algebra equations and Greek symbols we haven't seen since university. On the other extreme, you have "Look ma, it can do..." marketing: flashy demos that glorify specific products without explaining how they work. Neither is helpful if you want to understand the AI/ML space as an architect. You don't necessarily want to train your own models from scratch, but you also don't want to settle for just calling a "black box" API without understanding the implications. As the old engineering adage goes: **you must understand at least two layers of abstraction below the one you are currently working in**. For the past two years, I've been collecting notes to build a mental map of this space—primarily to guide my own software architecture decisions. Last year, I converted some of those notes into a talk called [AI for Java Developers](https://youtu.be/UjBUHdfUjVM){rel=""nofollow""}. When it placed in the top ten talks at DevBCN 2025, it became clear that I wasn't alone. Many developers are looking for a practitioner's perspective: the practical pros and cons of different implementation patterns. ### The Notes Collection "Why don't you just publish your notes? You know, as good, old-fashioned knowledge-sharing," someone asked me recently. I've decided to do exactly that. I've realized that waiting for the "perfect" expert to explain this is a mistake—the industry needs more input from people who actually ship code and maintain systems. Because my notes are currently too chaotic to share as-is, I'm committing to converting them into a series of coherent blog posts. My goal is one post per week, probably on Mondays so I can prep it over the weekend. I'm not launching a Substack or a YouTube channel to build an audience; I'm just sharing the mental models that helped me stop feeling like an outsider in this new stack. ### The Roadmap I'm posting this for a few reasons. First is to make a public promise that forces me to stick to the commitment. Another reason is to get early feedback on what you actually want to learn. If you're an application developer, please use the comments section below to tell me what would be most valuable to you. Are you wondering when to use a model vs. a traditional algorithm? Are you trying to figure out how to run these models locally instead of relying on a third-party API? I can't promise I'll have all the answers, but if I've hit that wall already, I'll share how I climbed over it. Finally, this post will serve as a table of contents for the series. I'll update the list below as each new post is released. - [AI/ML Models Are Not Libraries](https://MilenDyankov.com/blog/2026/03/AI-ML-Models-Are-Not-Libraries) - [A Trip in the AI/ML Model Formats Jungle](https://MilenDyankov.com/blog/2026/03/A-Trip-in-the-AI-ML-Model-Formats-Jungle) - [Anatomy of a Model - the Developer Perspective](https://MilenDyankov.com/blog/2026/03/Anatomy-of-a-Model-the-Developer-Perspective) - [The Inference Engine - Bringing Models to Life](https://MilenDyankov.com/blog/2026/04/The-Inference-Engine-Bringing-Models-to-Life) I've added a "Get new posts by email" feature to the blog section of my website for those who want notifications, but you can just as easily use the RSS feed or track my social media posts. # AI/ML Models Are Not Libraries The first time I wanted to use a model in my own application, I expected the experience to be similar to using a library or a framework. It seemed [Hugging Face](https://huggingface.co/){rel=""nofollow"" target="\_blank"} was the Maven Central or the npm *(or the CPAN for my old Perl friends)* of the AI world. I was under the impression that models were language-agnostic and we could simply download them and interact with them via a standard API. It turns out it's not that simple. To make sense of a model repository, we need to take a step back and understand what a model actually *is*. ## A Use Case for a Model Let's assume we need a feature in a text editor that warns writers if a sentence is too long or too short. These terms are subjective; there is no one-size-fits-all rule, and opinions vary on what constitutes "correct" length. So, how do we write a program that takes any sentence as input and outputs "too short," "too long," or "OK"? If we had a large enough dataset of unique sentences, we could ask humans to label them. Statistically, a large enough dataset should allow us to make an educated guess about how most readers will feel about a new sentence. However, we don't necessarily know *why* the readers classified them that way. A simplistic approach would be to assume character count is the deciding factor and hardcode the logic: ```ts if (sentence.length() < min) return 'too short' if (sentence.length() > max) return 'too long' return 'OK' ``` But this might be the wrong assumption. What if the word count matters more? Or the average length of the words? What if syllables or punctuation play a role? Manually extracting this logic and hardcoding it is a fragile, uphill battle. What we need instead is a **classification model**. ## Building a Model As software engineers, we want to map a problem directly to code: input text, perform logic, output a value. But when the "logic" is a black box of human intuition, we can't generate it by trying things out until it works. Or, can we? If we can reduce the problem to a function that receives numbers, performs calculations, and produces other numbers, the challenge shifts from a software engineering to mathematical one. ### The Output Turning the output into a number is straightforward: a `score` between `0` and `1` allows us to use configurable thresholds to produce the label we need. ### The Input For the input, we've already considered calculating length, word count, etc. These are what the AI world calls ***features*** - numerical representations of object's characteristics. Let's say we represent each sentence as `[number_of_characters, number_of_words]` collection of features. So `[42, 7]` represents any sentence that has 24 characters and 7 words. In a more general form, this is a `[f1, f2, ..., fN]` sequence which in math terms is a ***tensor*** (multi-dimensional vector). Finding the features that are somehow significant is a task for a data scientist, not software engineer. Figuring out what what formulas to apply on those tensors is what mathematicians are best at. ### The Math What we need from the math is a score per sentence that we can compare to the score obtained from our readers. Say we ask a friendly mathematician who knows such formula and we get: ```text score = (w1 * f1) + (w2 * f2) + ... + (wN * fN) + γ score = 1 / (1 + e^(‑score)) ``` Here `[w1, w2, ..., wN]` are called ***weights*** and `γ` is called ***bias***. You can think of the weights as "tuning knobs" for specific features while the bias is a sort of a "global knob". I'm not a mathematician and I make no claims this is the right approach to solve this. It is just an example that calculates the dot product distance between the weight vector and the feature vector (with some bias). The idea here is that there exists a weights vector (and bias) that represent "the perfect length" of a sentence. These weights are the values for that "perfect" sentence we don't know and need to "learn". ### The Training To "learn" the actual values of the weight vector `[w1, w2, ..., wN]` and the bias `γ` , we could start with random ones and run multiple iterations over our dataset. After each we check how far our calculated score is from the human-labeled score and adjust the weights accordingly. We iterate until we find a combination that produces close-enough scores for most entries in our dataset. This iterative process is called ***training***. ::note{variant="soft"} The above is a massive oversimplification. In reality, training involves complex calculus and specialized tooling. :: Once we are satisfied with the accuracy, we export a **model**. ### The Model Artifact In essence, producing a model means storing in a persistent form the following: - The **weight vector**: `[w1, w2, ..., wN]` - learned during training - The **bias scalar**: `γ` - a constant to shift the output up or down regardless of the inputs - The **architecture**: a computational graph that defines the sequence and the shape of the mathematical operations (in this case: perform a linear dot-product, add the bias, and apply a sigmoid). The tricky part is how and where to store that. If mathematicians used Java, Hugging Face would have been the largest repository of serialized Java classes. Instead, it is largely "serialized Python". Jokes aside, serialization seems reasonable distribution practice if the sole recipients are fellow researchers who run the model in complete isolation. Sadly, historically that was the assumption, and that is how the model landscape has shaped. I plan to publish a dedicated piece on model formats but generally speaking, the published model artifacts depends on the training tool used. It could be a serialized data structure, a tool-specific format, an archive of files, or something else entirely. If we were to indeed train the model described above, and give it to the world - out best bet for interoperability is [ONNX](https://onnx.ai/){rel=""nofollow"" target="\_blank"}. The size of a model (both in-memory and on-disk) largely depends on the number of its ***parameters***.️ ### The Parameters What the AI world calls "parameters" is every learned value used in calculation. So, the above example is a super simple, 3-parameter *(two weights, one bias)* model. For reference, LLMs (Large Language Models) typically count parameters in the billions. To understand the discrepancy, let's imagine we want to split each sentence into words and have features *(and biases)* per word like `position` and `length`. Then we no longer have a vector but a matrix: ```text [ [position, length], ... [position, length] ] ``` Capping this to a maximum of 100 words per sentence, we would end up “learning” and storing 200 weights + 100 biases. In other words, this becomes a 300-parameter model. If we were to split further into syllables, we would end up with a tensor (3-dimensional array): ```text [ [ [position, length], ..., [position, length] ], ... [ [position, length], ..., [position, length] ] ] ``` Capping this to a maximum of 10 syllables per word gives us 2K weights + 1K biases, or a 3K-parameter model. Furthermore, we could introduce multiple layers and use the learned weights and biases from one layer as features for the next, which means even more parameters. We are still far from billions, but the exponential growth of parameters should be obvious by now. Complex models (like LLMs) typically have architectures with orders of magnitude more complexity, include multiple layers, use various activation functions, and store other information, such as vocabularies and embeddings. ## Using a Model Conceptually, all models are persisted chains of mathematical functions and numeric values. They accept numeric input and compute numeric output. ### Inference Models are not like software libraries. You don't add them as a dependency to your application and call their API. The process of making the model do its work is called ***inference*** and at minimum we need an ***inference runtime*** capable of loading the model into memory and executing the computation graph. It's the inference runtimes that an application can use as an library, not the model itself. But not all runtimes can run all models. What's more, some only work within specific programming language ecosystems. Therefore, the most common scenario today is to have a tiny wrapper around such runtime that exposes the model via remote services. The term ***inference provider*** refers to those who offer such services. ### Preprocessing and Postprocessing The simple example we discussed above illustrate a few important facts that are often misunderstood: 1. The model itself never uses the actual use case data—text, image, sound, and so on. It's the responsibility of the program executing the model to convert the data into the tensor (scalar, vector, matrix, ...) that the model expects. This is often called ***preprocessing***. 2. The model doesn't know the purpose of the computation it performs. In the example above, the model expects an input of the form `[number, number]`. We know these represent "number of characters" and "number of words," but the model doesn't. We could get an image, extract the number of pixels and the number of unique colors, and pass those to the model. It would happily do the calculation and produce an outcome. It just wouldn't have the meaning we expect it to have. 3. The model doesn't know the meaning of the outcome. It calculates a numeric tensor (scalar, vector, matrix, ...) as a result. It's the program executing the model that needs to interpret this value and convert it to "meaningful" result. This is known as ***postprocessing***. ## Summary Hopefully this post helps you understand better what models are and why we can't simply download one and embed it in our code. As frustrating as it is for an application developer, it's a perfectly normal situation for the academic world. Keep in mind people who build models are not software engineers. The requirements we must address while building libraries and tools for mass adoption are typically out-of-scope for them. So, if you're for example a Java developer who finds a cool model on Hugging Face that does exactly what your enterprise system needs, don't celebrate just yet. Prepare yourself for a potentially disappointing discovery that it can only be used by some Python-specific runtime. If that is the case, hopefully there is room for another piece of critical infra in your architectural diagram. --- ::note --- color: soft icon: mdi-light-book-multiple title: AI for Application Developers Series --- The post is part of the [AI for Application Developers](https://MilenDyankov.com/blog/2026/03/AI-for-Application-Developers) series - my personal notes on various AI topics converted to blog posts. Please do not hesitate to **correct** me if I got something wrong, **contribute** if something is missing, **ask** me to clarify or simply **share** your experience and views. :: # A Trip in the AI/ML Model Formats Jungle As developers, we can immediately recognize the structure of a `.csv` file and how it differs from a `.md` one. Want to experience the frustration of someone completely lost when faced with those extensions? Then take a trip through the `.bin`, `.pb`, `.h5`, `.nemo`, `.dduf`, `.pt`, `.pth`, `.ckpt`, `.safetensors`, `.onnx`, `.gguf`, ... jungle of model formats. In the section "[The Model Artifact](https://MilenDyankov.com/blog/2026/03/AI-ML-Models-Are-Not-Libraries#the-model-artifact)" of the previous post, I wrote that producing a model means storing the weights, biases, and architecture in a persistent form. Well, that was an oversimplification. In reality, what's actually stored and distributed varies considerably. ## Weights-Only Formats Sometimes the architecture of a model *(more on that in follow-up posts)* is "well known" or follows a standard. Moreover, the ML/math libraries that data scientists use already have at least the primitives for executing such architectures. In the simple example from the previous post, we used the "dot product" and "sigmoid" functions—both of which are available in every single ML framework. So, if we only need to share our model with fellow researchers who use the same framework, we don't need to embed those primitives within the model itself. All we share are the weights and biases we learned. The non-Python application developer's nightmare is that the vast majority of models are "distributed" in such formats. This is also the primary driver behind the common perception that Python is the only viable option for running ML/AI models. Here are the weights-only formats that you may see most often in model repositories: - **`.bin` / `.pth` / `.pt`:** These are essentially serialized Python objects. They are one of the most common formats because many researchers use PyTorch. The original Python source code is needed to define the "class" before the data can be loaded. - **`.safetensors`:** The successor to `.bin` addressing a security flow in those above that allows execution of malicious code upon loading. - **`.ckpt`:** It's a snapshot of the model's state at a specific point in time. Like a core dump, it's heavy and often contains "optimizer states" that you don't actually need for inference. ## Mostly Self-Confined Formats Storing the architecture within the model is what enables portability. Such model formats don't require the same framework used during training to run; instead, they target a specific inference runtime. Some runtimes are available as libraries that can be embedded in or used by different languages and software stacks, while others can run standalone. If the architecture is well-known and natively supported by the targeted inference runtime, the model format may only store a reference to it. Otherwise, it may store a definition of the entire computational graph that the runtime instantiates. As an application developer, those are your best friends: - **`.onnx` ([Open Neural Network Exchange](https://onnx.ai/){rel=""nofollow"" target="\_blank"}):** It's an open standard for machine learning interoperability that defines a standard set of operators. A model in `.onnx` format can run on the `ONNX Runtime` in C#, Java, or even in the browser via WebAssembly. - **`.gguf`:** Currently the king of local LLMs. It is a single-file format that stores the weights, the architecture, and even the metadata (like the tokenizer). It's processed by the [ggml](https://github.com/ggml-org/ggml){rel=""nofollow"" target="\_blank"} library, which is mostly used by [llama.cpp](https://github.com/ggml-org/llama.cpp){rel=""nofollow"" target="\_blank"}, but it could hold other model architectures too. The following are also mostly self-contained but somewhat less portable/embeddable: - **`.pb` :** The format used by TensorFlow. It stores the computational graph as a frozen protobuf file. Like ONNX, it's designed for production environments and can be run via TensorFlow C++/Java (Legacy). - **`.tf` / No extension:** This is a directory structure. It contains a `saved_model.pb` (the graph) and a `variables/` folder (the weights). It is a standard for production deployment via TensorFlow C++/Java/Go SDK - **`.tflite` / `.litertlm`:** This is a highly optimized format for mobile and edge devices. The [LiteRT](https://ai.google.dev/edge/litert){rel=""nofollow"" target="\_blank"} runtime has libraries for Kotlin/Java, C/C++, Swift / Objective-C, and even in browser JS/TS (via WASM) - **`.dduf`:** It stands for **Diffusion Unified Format**, and it was created as self-contained format for diffusion models which normally consists of few sub-models (image decoder, text encoder, generator, denoising, ...). It's essentially an uncompressed ZIP file meant to be used with [`diffusers`](https://github.com/huggingface/diffusers){rel=""nofollow"" target="\_blank"} Python library. ### Why "Mostly"? Inference runtimes *(or rather the application they run within)* often require more than just the model itself. They typically expect some configuration to be passed together with the model. Many models assume the input was prepared using certain tools like vocabularies and tokenizers. As a general rule, such resources do not make it into the model file and are distributed separately. You can think of those as "dependencies" or "sidecars". Here are the most common ones: - **`tokenizer.json` / `tokenizer_config.json`:** They contain the mapping table between words and token identifiers and are essential for LLMs. The application calling the runtime uses it to convert the user input into token IDs used in computation *(more on that in a separate future post)* - **`preprocessor_config.json`:** They usually accompany image/audio models. It tells the app how to resize, crop, or normalize an input before the model can process it. - **`config.json`:** Think of this as the `.env` or `application.properties` for the app/wrapper running the inference engine. ## Inspecting the Model's Content As application developers who mostly just want to use models, we typically only care about the contract: the inputs and outputs. But sometimes those are not clear, and you might need to "peek under the hood" of a model file. When that happens, head over to [Netron.app](https://netron.app/){rel=""nofollow"" target="\_blank"}. This web app is a visualizer that takes a model file and renders it as a directed graph. It executes entirely in your browser and works with almost all model formats. Point it at a model, and it shows you the "flow" of data. It's a bit like using a decompiler or a profiler. It's especially helpful when dealing with a model that's not well documented, and you need to verify exactly what inputs the model expects and what the output shapes look like. ## Converting Between Model Formats At the time of writing, the AI/ML ecosystem largely distributes models as weights-only or in formats specific to Python frameworks. This leaves application developers in other fields with two choices: incorporate Python infrastructure into their architecture, or convert the model to a portable format. The good news is that most frameworks used to train models can export them as `.onnx`. The bad news is that doing so will likely require some Python scripting and familiarity with those libraries. Or, if you're feeling brave *(I'm trying to avoid the word "reckless" here)* you can ask AI to do it for you. Conceptually, the process is straightforward: you use PyTorch, TensorFlow, JAX, and so on, to load the model from its original format and then export the loaded model as `.onnx`. In practice, however, this may require a much deeper understanding of the model architecture than an application developer is willing to learn. Especially when various things start to break. ## Open Models & Model Licensing Many models, especially the popular LLMs, are proprietary and not publicly available. The format in which they are stored is irrelevant for an application developer, as we can only use them through the APIs and apps their vendors *(or partners)* provide. However, there are plenty of open models that we can download and run ourselves or embed in our apps. The meaning of "open" in this context is a bit tricky. While many vendors claim to release open-source models, they often actually release models with open weights. - [Open Source AI Definition (OSAID 1.0)](https://opensource.org/ai/open-source-ai-definition){rel=""nofollow"" target="\_blank"} mandates that a model grants the freedom to use, study, modify, and share it. To satisfy those expectations, a model must provide the weights, the parameters, the training code, and a detailed "provenance" of the data used, including how it was cleaned and labeled, so a skilled person could build a substantially equivalent system. - [Open Weights](https://opensource.org/ai/open-weights){rel=""nofollow"" target="\_blank"} specifies, on the other hand, that only the final weights and biases of a trained neural network need to be made available. As with libraries, each model usually comes with a license. Some use traditional licenses like Apache 2.0, MIT, or GPL. Others have custom licenses with additional restrictions. For example, the Llama model licenses famously state that if you have more than 700 million monthly active users, you need to request a special license from Meta. ## Model Repositories As of 2026, [Hugging Face](https://huggingface.co/){rel=""nofollow"" target="\_blank"} is the largest model repository, hosting millions of models, datasets, and demo apps (Spaces). It's often referred to as the "GitHub" of AI, although if you're old enough to remember those days, it might bring back memories of the SourceForge era of open-source projects. As an application developer, this is your best bet - just be aware that you are a visitor in a data scientist's land. [ModelZoo](https://modelzoo.co/){rel=""nofollow"" target="\_blank"} is a curated collection of various models with one major advantage: unlike Hugging Face, it's not flooded with experimental or half-baked models. However, its benefit comes with a caveat - most of these models point to GitHub repos with source code, rather than exported model artifacts. The [ONNX Model Zoo](https://github.com/onnx/models){rel=""nofollow"" target="\_blank"} is a curated collection of pre-trained models in the ONNX format. Although the actual collection is now maintained on Hugging Face, the linked legacy README still provides valuable insights about the collection. [Kaggle](https://www.kaggle.com/){rel=""nofollow"" target="\_blank"} is primarily used for AI competitions, but it also hosts a vast library of models and datasets. ## Summary The current AI hype and the LLM supremacy wars between the famous vendors paint a picture where using pricey services is the only option. A good software architect should remember that it isn't. Sometimes a smaller, specialized model is a much better fit. Sometimes there are strict compliance and data residency requirements to adhere to. It's good to observe the AI ecosystem evolving towards openness, but there are roadblocks. Figuring out what those model files are and how they're meant to be used was one such roadblock for me - hopefully it won't be for you. There are repositories to find open models. Don't let the various unfamiliar formats scare you off. If you're lucky, the model you need will already be available in a portable format like `.onnx`. For local LLM models, `.gguf` will let you run them almost anywhere, thanks to `llama.cpp`. Even if you only find a weights-only format, you still have an option to convert it to a portable one. It may require some Python skills, but it's not rocket science. --- ::note --- color: soft icon: mdi-light-book-multiple title: AI for Application Developers Series --- The post is part of the [AI for Application Developers](https://MilenDyankov.com/blog/2026/03/AI-for-Application-Developers) series - my personal notes on various AI topics converted to blog posts. Please do not hesitate to **correct** me if I got something wrong, **contribute** if something is missing, **ask** me to clarify or simply **share** your experience and views. :: # Anatomy of a Model - the Developer Perspective In the old days, every IT organization had a dedicated, almost sacred role: the DBA (Database Administrator), often informally known as the "Gatekeeper of the Schema." These individuals ensured that the schema adhered to 3NF (Third Normal Form), that the appropriate fields were indexed, that no foreign keys were missing, that data was accurately partitioned, and that the overall structure resembled a proper "Star" or "Snowflake" schema. Most of this was a mystery to software developers who simply wanted to store and retrieve data via SQL, but it was crucial from a resource efficiency perspective. It's a similar situation today. ML experts are the "Gatekeepers of the Model", and they happily use mystique terms like transformers, RNN, CNN, CLIP, BERT, GPT, LLaMA, ResNet, Whisper, etc. that mean absolutely nothing to a software developer. We are told it's not our concern and asked to just use the API. And when we complain about getting the wrong results, the explanation is that we used an unoptimized ~~query~~ prompt/context. Déjà vu! However, AI/ML models share conceptual similarities with databases in several ways. You don't need to know how to build a database to use one, but it helps to understand in what way a relational database differs from a document store, a key-value store, or a graph database. The more you know about indexes, foreign keys, properties, labels, edges, vertices, and so on, the easier it is to pick the right database for the use case. It's the same with models. ## The Skeleton As discussed in [this previous post](https://MilenDyankov.com/blog/2026/03/AI-ML-Models-Are-Not-Libraries#using-a-model), all models are essentially complex chains of mathematical functions that accept numeric input and compute numeric output. That is yet another oversimplification, as internally, most models have a layered structure and specific expectations regarding the data format and structure. Conceptually, this is similar to how different databases expect data to be structured in tables, JSON documents, key-value pairs, or directional graphs, and how that shape impacts what operations are possible or easier to perform. ### Ingestion While it's not a layer within the model, it's sort of an API for the model, or a contract, if you will. A model doesn't simply accept a bag of numbers. It expects those to be in a certain shape. It's the responsibility of the code executing the model to properly prepare the data. For example, Natural Language Processing (NLP) models expect a sequence of token IDs as an input. The caller needs to use a specific tokenizer to convert the raw text it has into such a sequence. Similarly, Computer Vision (CV) models may expect a sequence of fixed-size matrices. The code executing the model needs to slice an image or a video frame accordingly and prepare the matrices. ### Projection Even though the input is already numerical, most models don't work directly with those numbers. They "project" them into vectors that carry some semantic meaning. So when a GPT model gets `[15546, 527, 499, 30]` *(the IDs of the `['How', 'are', 'you', '?']` tokens)* it checks its id-to-vector lookup table and gets ```json [ [ 0.0154, -0.0211, 0.0892, -0.0441, 0.0123, ... ], [ -0.0031, 0.0456, -0.0112, 0.0334, -0.0089, ... ], [ 0.0221, -0.0104, 0.0558, 0.0112, -0.0445, ... ], [ -0.0552, 0.0012, -0.0334, -0.0021, 0.0991, ... ] ] ``` Sometimes the input values are not within a finite range, so a lookup table is not an option. In such cases, the model may use a function to project them to vectors. Ultimately, though, it's the tensors holding the semantic meaning and learned during training that the processor works with later on. ::note{variant="soft"} In some architectures, the boundary between these layers is fluid. For example, in Computer Vision, the "Projector" is often just the very first layer of the "Processor". :: ### Processor This is the backbone of the model - the place where the heavy computation happens. The goal is to find meaningful relationships between the individual data points in the input. I'll not pretend I understand how this works in detail. What is important is that it produces some sort of raw relationship map. The way I like to think about the processor is as if it were a stored procedure (or a script) in a database that does the heavy lifting and produces a giant table/view/graph/object/document. The information I want is there, but mixed within volumes of other data. I could extract it myself, but I'd rather have the model do it for me. ### Head This is where the extraction happens. At this phase, the model converts the giant tensor from the processor into something simpler and more aligned to the caller's expectations. Having a 'detachable' head allows ML engineers to reuse model architectures for different purposes. For example, the processor of a model designed to deal with text will produce a giant relationship map between tokens. Then different heads can be attached to this model to narrow down its purpose. Here are some examples of heads that operate on the same result from the processor to deliver different results: - Flatten the giant tensor to a single vector representation of the input. We could use such a model to get the vectors for different texts, calculate the distance between them, and tell how semantically similar they are *(semantic search)*. - Map the processor outcome to some predefined categories *(classification)* or terms *(named entity recognition)*. - Compute scores for each token in a fixed-size vocabulary representing the likelihood of that token being the next in the sequence *(prediction)*. - Generate another sequence that is shorter *(summarization)* or represents different application level data *(translation)* while keeping the same meaning. ## The Architectures The skeleton above describes the layers but doesn't tell what exactly sits in them. If it were referring to applications, it would be like saying we have `parser -> adapter -> service -> formatter` flow. It gives us a rough idea, but it is not enough to implement an actual solution. Defining what those layers do is what the ML world calls "Architecture". Mostly, it describes the specific computation algorithm the Processor is using, but that often implies how the other parts are structured. ::note{variant="soft"} The terminology in the ML world can be very confusing for an outsider. It's really hard to know exactly what a term means without the full context. I use "Architectures" here as that one seems to be the most popular, but you will also see "Models", "Design Patterns", or simply "Neural Networks". And if you dig into those in a bit more detail, you may start thinking they are just algorithms. It's no different than how we developers often call the same thing a module, a library, and a framework depending on the context. :: ### Transformer This architecture is designed to process relatively long sequences of data points *(tokens, parts of an image, slices of a sound wave, ...)*. During training, the model builds a map of semantic connections between all entries in the training sequences. The computational graph is a multilayered math engine performing a series of calculations between the learned weights and any given sequence of data points. The result is a mathematical representation of the relationships between the data points within the input and within the model's vocabulary. There are three flavors of the transformer architecture. #### Encoder-Only This architecture computes a complex tensor representing the relationships and stops there. To do that, the processor typically examines the entire sequence at once (comparing every data point to every other point). The output depends on what head is attached. It could be a single vector representing the semantic meaning of the entire sequence. It could be a scalar identifying one of multiple predefined categories. Or it could be binary-like classification *(spam / not spam)*. Models with this architecture are primarily used in semantic search, similarity assessment, classification, and named entity recognition (NER) use cases. #### Encoder-Decoder This architecture consists of two computational graphs: a headless encoder, as described above, and a decoder. Using such a model is a two-phase process. The code executing the model first runs the encoder to get the tensor representing the relationships in the input sequence. Then it runs the decoder passing that tensor. The decoder's compute engine is designed to compute another vector that describes what the next data point should be like. Then the head converts this to a value that the calling code can handle. If more data points need to be generated *(text generation, sequential predictions)* then the code calls the decoder in a loop, passing every time the tensor from the encoder and the sequence of tokens it builds from all previous iterations. The primary use for such models is translation and summarization. Their ability to see relationships between all data points makes them extremely accurate. The drawback is that accuracy comes at the price of significant memory consumption. #### Decoder-Only In this architecture, there is no dedicated encoder. Instead, the decoder's processor computes the semantic representations of the input sequence on the fly during the generation phase. It does so by processing one data point at a time *(even though it technically has all)* and comparing it to all previous points. After it processes the last point in the input sequence, it produces a vector describing the features of the next entry. The head then converts that vector to the expected target value. In text generation *(the most common use case for this architecture)*, the head produces logits - a matrix of scores for each token in a fixed-size vocabulary representing the likelihood of it being the next in the sequence. The code calling the model picks the most probable one, adds it to the input sequence, and executes the model again. Because there is only one processor, these models are easier to train on large datasets, tend to be more flexible, and scale better than Encoder-Decoder ones. Because they never "look forward", appending tokens to the input doesn't change the vectors already computed. This makes the once-computed vectors cacheable, which is great for chat-like scenarios. This is the architecture most, if not all, Large Language Models (LLMs) employ. ### RNN/LSTM Like transformers, Recurrent Neural Networks (RNNs) operate on sequences. Unlike a transformer, they don't get the entire sequence at once. Their input consists of a single item and the state computed during the previous iteration. Their output also has two parts - the state and whatever the head produces. The code executing the model is responsible for maintaining that state between iterations. The Long Short-Term Memory architecture (LSTMs) is a specialized version of the same concept but with an improved memory management system. In RNNs, the state is overwritten at every step. In an LSTM, there are three logic gates that control what information is removed, retained, and added. While largely replaced by transformers for text, this architecture is naturally suited for time-series data or streaming telemetry where the order of events is the most critical piece of information. ### SSM The State Space Models (SSM) architecture is an evolution of the RNN concept, aiming to solve the "forgetting" and performance issues with long inputs. Similarly, it operates on a sequence of data points and needs the code to maintain a state from previous iterations. Therefore, the execution flow is identical to an RNN. The code passes the current data point and the previous state to the model, and it returns the result plus a new state to store for the next call. The difference is how this state is calculated. I'll spare you the details *(mostly because I have trouble understanding them myself)* but apparently SSM's computation graph is designed in a way that can better decide how to handle the new data point using less computation power. The fact that they don't carry the entire context is both an advantage and a disadvantage compared to transformers. [This paper](https://arxiv.org/html/2601.01237v1){rel=""nofollow""} benchmarks them against transformers and shows *"12.46× better memory efficiency and 10.67× faster inference at 4,096 tokens, with the efficiency gap growing as sequence length increases"*. The same paper also indicates that \_"Transformer's explicit attention mechanism provides interpretable token-level attribution, and prior work demonstrates superior performance on tasks requiring precise associative recall"\_. ### CNN Convolutional Neural Networks (CNN) process grid-like data by looking for local patterns. They are mostly used with images, but they can process anything that can be represented as a numeric grid - audio waves, text sequences, sensor data, and more. The grid size is fixed per model. Historically, a `224 x 224` shape *(roughly 50K data points)* was kind of the golden standard. Today's models may offer bigger grids like `512 x 512` or even `1024 x 1024`, but the hardware requirements for processing such a scale grow dramatically. To meet the ingestion requirements, the code running the model has two options: scale the input data to the expected size or chunk it into properly sized fragments and run the model independently for each. Also, the values in the cells must be in the `0.0 - 1.0` range, so the code needs to normalize those as well before executing the model. The model projects the input grid into a feature map by sliding a smaller *(e.g., `3x3`)* mask over it, performing a convolution. This is a fancy way of saying it calculates a dot-product at every possible stop to see how well any given grid fragment matches a specific pattern. The resulting tensor describes the features of all (overlapping) fragments of the input matrix. Then, in a number of layers, the model performs complex computations to find relationships between those fragments. The outcome is a dense, high-dimensional vector representing every pattern found. Finally, different heads can be attached to extract meaningful data from those patterns. For example, a classification head may map it to scores like `[Cat: 0.98, Dog: 0.01, Hotdog: 0.01]`. A detection head may return the coordinates of where a pattern was found. CNNs are the industry standard for image classification, object detection, and facial recognition, but could also be useful and efficient for speech processing, fraud detection, stock market prediction, and more. ### ViT Technically, Vision Transformer (ViT) is an adaptation of the transformer architecture for vision. Instead of taking a fixed-size data grid as an input like CNNs do, it expects a sequence of small-size data grids *(e.g., `16x16` each)* flattened to 1 dimension. At this point, the input is exactly what a transformer needs in order to start comparing every data point to every other data point and building a tensor of semantic knowledge. But there is a catch. The sequence misses very important information about the positioning of the data points. There is nothing that says "this data point is top-right from this other data point". To solve that, the model has an internal grid of slots (e.g., `224 x 224`) where it puts the data points. Thus, the input sequence length must match the number of slots in that grid. It also means that input shapes like `112 x 448` and `448 x 112` are essentially a `224 x 224` ones for the model. An evolution of that architecture is the "Native Dynamic-Resolution ViT" which doesn't use internal grids. Instead, it allows the caller to provide dimensions and relative positions of the data points. This makes it possible to process input images with variable resolutions. ### CLIP Contrastive Language–Image Pre-training (CLIP) is what some people call a Meta-Architecture. Conceptually, it is a dual encoder that uses a transformer for text encoding and ViT or CNN for image encoding. A CLIP model is typically trained on a set of natural language fragments and a set of images and learns which mappings between those make sense and which don't. While each produces an embedding representing the input independently, they share the same [latent space](https://en.wikipedia.org/wiki/Latent_space){rel=""nofollow"" target="\_blank"}, so vectors for texts and images that are semantically similar are closer to each other. In reality, those are often distributed as a pair of two models (text and vision) that are trained to work together. From an application developer perspective, you run two models and get whatever their heads produce *(usually an embedding)*. Then the app plays the role of the final head. It could measure the distance between two vectors to estimate similarity. Or it could compare multiple text embeddings to one vision embedding to find the best description for an image. Occasionally, there are combined unified releases that accept both the text and the image, run the two flows internally, and process the result through a specialized head. ### Latent Diffusion This is the architecture for image generation, and it can literally flood you with complex math formulas if you Google it. Conceptually, though, it is reversing the process of adding noise to a dataset. During training, a model gets datasets and gradually adds noise to them. It does this in `timesteps` *(e.g., 1000 iterations)* and learns a transition pattern from a meaningful dataset to "random" noise. Then, during inference, it is given some noise and a semantic representation of the desired outcome, and it works backwards to "restore" a dataset. Technically, we can execute such a model with just noise. It will randomly select a tensor that represents the outcome and work towards it. For all practical terms, though, we want to provide that tensor ourselves. We can create it from: - Text input like "Generate an image of a coffee cup" by using a CLIP model - Source image by using a Variational Autoencoder (VAE) Encoder model - both combined Many application developers *(past me included)* at this point think that we just send that data to the model and get the result. It's more complicated than that. The model has learned the migration paths from `timestep` to `timestep` and going back straight from 1000 to 1 would mean loosing most of the details. That doesn't mean we need to run all `timesteps` in reverse, but our code must orchestrate the generation. For that, we need a scheduler that runs the model in a loop *(e.g., 20 or 50 times)* and on each iteration passes the current noisy latent tensor and the `timestep`. In return, it gets a tensor telling it how much noise to "subtract". The model usually doesn't have a head because we need that latent tensor for the next iteration. Thus, the code running the process is typically the head. To convert that final latent tensor into a pixel map, we use yet another model *(e.g., VAE Decoder)*. ## The Implementations I find that having a basic understanding of the architectures is essential for using models properly and efficiently. In practical terms, however, we look for an implementation not by architecture but by what problem it solves. So here are some implementations grouped by their primary use case. ### Text Generation & Reasoning (LLMs) These are almost exclusively Decoder-Only Transformers. The most prominent model families in that space are **GPT** by Open AI, **Claude** by Anthropic, and **Gemini** by Google. They are all proprietary, and we cannot get the model files. We can only interact with them via APIs. The open-source / open-weight models families list includes **Llama**, **Mistral / Mixtral**, **Grok**, **GPT-OSS**, **DeepSeek**, **Qwen**, **MiniMax**, and many others. While mostly used via inference providers or apps like `Ollama` or `vLLM`, we can technically interact with them directly from our code. The issue is that those are very large models that require enormous GPU power and hundreds of GB of memory. We can still run quantized versions of them locally, though *(more on that in upcoming posts)*. ### Computer Vision & Image Analysis These models power everything from FaceID to autonomous driving. If that's what your app needs, take a look at - **ResNet:** This is the classic CNN implementation. It is an open-source model family. If you need to classify images on an edge device with no GPU, this is your go-to. - **Segment Anything (SAM):** An open-weight model from Meta using a ViT architecture. It can "cut out" any object in an image. - **YOLO (You Only Look Once):** The gold standard for real-time object detection using a CNN. It is open-source and can run on a live video feed at 60+ FPS, even on modest hardware. ### Audio & Speech Processing Models that bridge the gap between sound waves and text are often implementations of the Encoder-Decoder Transformer or RNN architectures. The ones you'll likely hear most often about are - **Whisper (OpenAI):** An open-weight implementation of an Encoder-Decoder Transformer. - **Qwen2-Audio:** An open-weight models that can "hear" audio and respond to it without a separate STT (Speech-to-Text) block. - **CosyVoice:** An open-source model for low-latency streaming audio ### Image & Media Generation These are complex orchestrations, usually involving a Latent Diffusion loop. **DALL-E** from Open AI is the precursor but it's proprietary and only available through ChatGPT or the OpenAI API. **Stable Diffusion** is the most popular open-weight implementation. Developers have built massive ecosystems around it (like [Stable Diffusion web UI](https://github.com/automatic1111/stable-diffusion-webui){rel=""nofollow"" target="\_blank"} or [ComfyUI](https://www.comfy.org/){rel=""nofollow"" target="\_blank"}) that allow wiring up the U-Net, VAE, and CLIP models manually for total creative control. **Flux (Black Forest Labs)** is a state-of-the-art open-weight model that combines diffusion with a transformer backbone. It produces much higher quality text-within-images than older versions of Stable Diffusion. ### Semantic Search & Embeddings This is a crowded space, but those that you should know about are - **all-MiniLM-L6-v2:** The "classic" Encoder-Only Transformer. It is tiny (only 22M parameters) and incredibly fast. - **BGE-Small / BGE-Base (BAAI):** The modern successor to MiniLM. It uses a similar Encoder architecture but was trained with much more advanced techniques. - **Nomic Embed:** An open-source family of models that are incredibly popular for text-only RAG, but they also offer multimodal versions. They are known for "Matryoshka Embeddings", which lets us shrink the size of the data (from 768 down to 64 dimensions) to save on database costs without losing much accuracy. - **OpenAI CLIP:** An open-weight CLIP (Dual-Encoder) model that started the trend of mapping different types of data (text, images, even audio) into the same vector space - **Jina CLIP:** A powerful open-weight alternative that has much better text encoder (JinaBERT) that can handle long documents and 80+ languages, making it much more practical for real-world apps like search through PDF files using images. ## Summary I remember when software developers had a mental equal sign between data persistence and DB2/Oracle! Today, we're much more discerning about choosing the right storage solution for the job, and it might not even be an RDBMS. Meanwhile, the "AI equals LLMs" mindset is gaining traction. While transformers are powerful and versatile, they're not always the best fit. When looking at the AI landscape, it's tempting to go with the LLM that scored highest in a benchmark, but we should focus on finding the right tool for the problem at hand. I hope this post helps you do just that - make more informed decisions about your AI solutions.️ --- ::note --- color: soft icon: mdi-light-book-multiple title: AI for Application Developers Series --- The post is part of the [AI for Application Developers](https://MilenDyankov.com/blog/2026/03/AI-for-Application-Developers) series - my personal notes on various AI topics converted to blog posts. Please do not hesitate to **correct** me if I got something wrong, **contribute** if something is missing, **ask** me to clarify or simply **share** your experience and views. :: # The Inference Engine - Bringing Models to Life What we learned from "[AI/ML Models Are Not Libraries](https://MilenDyankov.com/blog/2026/03/AI-ML-Models-Are-Not-Libraries)" is that models are essentially collections of numbers *(weights)* and, optionally, mathematical formulas. The "optionally" part is key, as we saw in "[A Trip in the AI/ML Model Formats Jungle](https://MilenDyankov.com/blog/2026/03/A-Trip-in-the-AI-ML-Model-Formats-Jungle)" that not all model files store the formulas themselves. Some formats are "Mostly Self-Confined," while others are "Weights-Only," expecting the application using them to "know" the underlying math. In "[Anatomy of a Model - the Developer Perspective](https://MilenDyankov.com/blog/2026/03/Anatomy-of-a-Model-the-Developer-Perspective)", we explored different architectures and their inputs and outputs. With that groundwork laid, we can now consider the inference process as a whole. By now, it should be clear that producing a meaningful *(to the caller)* output during inference is a combined effort between the model, the inference runtime, and the inference endpoint that provides access to it. ## Inference Components As with everything in software and IT, different people may interpret a term slightly differently when they use it. Here's what I mean by the terms you'll see later in this post. ### Compute Backend Those large and complex computation graphs often demand massive processing power and substantial amounts of fast memory. This naturally leads to the need for specialized hardware. A compute backend is the engine that executes the graph on a specific device (e.g., GPU, TPU, CPU). It implicitly includes the device’s own memory (GPU VRAM, TPU memory, etc.), as that’s where the tensors reside during execution. Below are some examples of popular compute backends. | **Compute Backend** | **Target Hardware** | **Primary Software Layer** | | ------------------- | ------------------------ | ------------------------------------- | | **CPU** | x86 / ARM Processors | Standard System RAM + OS Scheduler | | **CUDA** | NVIDIA GPUs | CUDA Cores + cuDNN + VRAM | | **MLX** | Apple Silicon (M-series) | Unified Memory Architecture + Metal | | **OpenVINO** | Intel CPUs/IGPUs/VPUs | OneDNN / OpenCL / Plugin Architecture | | **ROCm** | AMD GPUs | HIP / ROCm Kernel Drivers | | **TPU (XLA)** | Google TPU | XLA Compiler + libtpu + PJRT | ### Inference Runtime / Engine It's a specialized execution engine designed to run models in a production environment. It serves as the bridge between the high-level mathematical abstractions of a neural network and the underlying compute backend. Its responsibility is to load the model's weights into the backend's memory and then perform computations using that backend on new input. Here are some popular runtimes. | **Inference Runtime** | **Primary Target** | **Compatible Compute Backends** | **Programming Languages** | | --------------------- | ----------------------------------------- | ------------------------------------------------------- | --------------------------------------------- | | **ONNX Runtime** | Cross-platform / Generic | CPU, CUDA, TensorRT, OpenVINO, CoreML, DirectML, ROCm | Python, C++, C#, Java, JS/Node, Rust, Go | | **TensorRT** | NVIDIA GPUs | CUDA, DLA (Deep Learning Accelerator) | Python, C++ | | **OpenVINO** | Intel Hardware | Intel CPU, iGPU, NPU, FPGA | Python, C++, C | | **ExecuTorch** | Mobile & Edge | CPU (XNNPACK), CoreML (iOS), MPS (Mac), Vulkan, NPU | Python (Export), C++ (Runtime) | | **LiteRT (TFLite)** | Mobile & Web | CPU, GPU (OpenCL/Metal), TPU (Edge), WebGPU | Python, Java, Swift, C++, JS/TS | | **vLLM** | Data Center LLMs | CUDA (NVIDIA), ROCm (AMD), TPU, OpenVINO (Intel) | Python (Primary), C++ | | **llama.cpp** | Zero Dependencies. Run anywhere with C++. | CPU (AVX/AMX), CUDA, Metal, Vulkan, SYCL, ROCm/HIP, RPC | C++, Python (via bindings), Go, Rust, Node.js | ::note{variant="soft"} Not every inference runtime can run every model. For example, `llama.cpp` is designed specifically for `GGUF`-formatted models. :: ### Inference Service Inference runtimes work with numeric tensors. An inference service bridges the gap between them and the client's data. It's where the preparation for ingestion and the postprocessing of the model's result happen. Those can be modules within larger applications, libraries, standalone applications, web services, and so on. ### Inference Provider Not really part of what this post is about, but oftentimes these are also called "Inference Runtimes" *(heck, I've used that mental shortcut with my clients)*. A provider is all of the above delivered as a managed service. Often called "Inference-as-a-Service". - **The Cloud Giants** Google Vertex AI, AWS Bedrock, Azure AI provide the full menu: endpoints, enterprise security, and access to specialized backends (like TPUs or Inferentia) - **Specialized API Providers** like Together AI, Fireworks.ai, DeepInfra focus on "Software-Defined Inference." They often write their own custom kernels and scheduling logic to be faster than the generic cloud providers. - **Hardware-First Providers** like Groq, Cerebras, SambaNova are the ones that blur the lines most. They often market their entire cloud as a "Language Runtime" to emphasize that the hardware and software are one single, optimized unit. ## Inference Loops Most generative models (including LLMs) are designed to be called in a loop. It's the inference service that controls when the generation ends. ### LLM Inference A sequence diagram is probably the best way to visualize how all the inference components work together. Here is a conceptual LLM inference flow. ::mermaid --- :config: config code: ---%0Aconfig%3A%0A%20%20layout%3A%20dagre%0A---%0A%0AsequenceDiagram%0A%20%20%20%20autonumber%0A%20%20%20%20participant%20C%20as%20Client%0A%20%20%20%20participant%20S%20as%20Service%0A%0A%20%20%20%20Note%20over%20S%3A%20Initialization%20Phase%0A%20%20%20%20S-%3E%3ES%3A%20init%0A%20create%20participant%20T%20as%20Tokenizer%0A%20S-%3E%3ET%3A%20Init%20Tokenizer%20(Vocabulary)%0A%20%20%20%20create%20participant%20R%20as%20Runtime%0A%20%20%20%20S-%3E%3ER%3A%20Start%20runtime%20(Model%2C%20Preferred%20Backend)%0A%0A%20%20%20%20participant%20B%20as%20Backend%20(GPU%2FVRAM)%0A%0A%20R-%3E%3EB%3A%20Allocate%20VRAM%20%2F%20Set%20Kernels%0A%20activate%20S%0A%20%20%20%20S-%3E%3ES%3A%20accept%20requests%0A%0A%20%20%20%20Note%20over%20C%2C%20B%3A%20Inference%20Request%22%0A%20activate%20C%0A%20%20%20%20C-%3E%3ES%3A%20%22Who%20are%20you%3F%22%0A%20%20%20%20S-%3E%3ES%3A%20prepare%20message%0A%20S-%3E%3ET%3A%20encode(text)%0A%20%20%20%20T--%3E%3ES%3A%20token%20IDs%0A%0A%20%20%20%20loop%20%22%5Buntil%20%3C%7CEOS%7C%3E%20or%20Limit%5D%22%0A%20%20%20%20%20%20%20%20S-%3E%3ER%3A%20run(token%20IDs)%0A%20%20%20%20%20%20%20%20R-%3E%3EB%3A%20compute(tensors)%0A%20%20%20%20%20%20%20%20B--%3E%3ER%3A%20logits%0A%20%20%20%20%20%20%20%20R--%3E%3ES%3A%20logits%0A%20%20%20%20%20%20%20%20S-%3E%3ES%3A%20sample(logits)%20-%3E%20token%20ID%0A%20%20%20%20%20%20%20%20S-%3E%3ET%3A%20decode(token%20ID)%0A%20%20%20%20%20%20%20%20T--%3E%3ES%3A%20token%0A%20%20%20%20%20%20%20%20S-%3E%3EC%3A%20stream(token)%0A%20%20%20%20%20%20%20%20S-%3E%3ES%3A%20tokens%20%2B%3D%2042%0A%20%20%20%20end%0A%20deactivate%20C%0A%20deactivate%20S --- :: Keep in mind this describes a conceptual flow. It completely ignores aspects like performance, scalability, security, deployment architectures, and so on. In actual production systems, especially those under heavy load, we can't ignore those, and so the diagram would be somewhat different then. But these simplifications help illustrate the process. #### Startup / Initialization Phase 1. The service typically starts by examining the configuration and the environment. It needs to determine: - Which model(s) to use and how to access the artifact(s)? The model files could be bundled or downloaded on demand. - What inference runtime(s) can load the model(s)? - Which compute backend is best suited for the available hardware? - Which tokenizer does the model use? Generally, this information should be provided by the model creators, either in a configuration file or apparent from the distribution artifact. 2. The service loads the tokenizer. Internally, LLMs use token IDs, and the tokenizer parses the input text and converts it to those IDs. The tokenizer's vocabulary is typically provided with the model, often in the form of a JSON file mapping each known word (or word fragment) to a number. Most inference runtimes include libraries that allow instantiating a tokenizer from these files. 3. The service starts the inference runtime, providing the model artifact or its location, along with the preferred compute backend(s). 4. The inference runtime instantiates a computation graph as defined by the model and loads the model's weights into the selected compute backend's memory. It then waits for the service to initiate a computation process. 5. The service exposes a UI or API to receive inference requests from clients. #### Inference Request Processing 6. The service receives a request. The payload is a string. 7. The service performs standard checks to ensure the request should be processed (authentication, rate-limit, quotas, etc.). It may also enhance/change the message according to some policy *(spellcheck, anonymization, ...)*. 8. The service calls the tokenizer to convert the content into token IDs. It's crucial that the service uses the exact same tokenizer and vocabulary as the one the model used during training. 9. The service gets a vector of token IDs from the tokenizer 10. The service requests to start a computation session on the currently loaded inference runtime, passing the vector of token IDs. 11. The inference runtime executes the computation graph loaded from the model on the specialized hardware through the compute backend. 12. LLMs are typically "[Decoder-Only Transformers](https://MilenDyankov.com/blog/2026/03/Anatomy-of-a-Model-the-Developer-Perspective#decoder-only)" and their head produces logits. 13. The inference runtime returns to the service the logits. 14. The service selects the next token based on the logits returned. Typically, it first applies a `softmax` function to convert them to probabilities (decimal values between 0 and 1). Then it reduces the list to just a few token IDs using "top-p" (the smallest set of tokens whose cumulated probability is `p`) or "top-k" (the `k` tokens with the highest probability). It then randomly draws a token from the reduced list. 15. Assuming it is a streaming service, it calls the tokenizer to de-tokenize the ID 16. The service gets the actual word/fragment from the tokenizer. 17. The service sends the actual word/fragment back to the client. 18. If the selected token is the model’s `<|EOS|>` (End-Of-Sequence) token, then the service completes the session with the client. Otherwise, it appends the newly obtained token ID to the current vector of token IDs and repeats the process from step 10. ### Latent Diffusion Inference Another example of models relying on loops for generations is diffusion models, frequently used for image generation. Conceptually, those are not a single one but a combination of models. Typically, a `CLIP` model is used for understanding the textual input, a `U-Net` one for calculating the noise reduction, and a `VAE` for decoding the tensor into pixels. Starting with a 100% noise, the service needs to invoke the inference runtime in a loop until a final result is achieved. This might be after a predefined number of iterations, or based on an algorithm that checks if the noise level falls below a certain threshold. This results in a rather complex flow: ::mermaid --- :config: config code: ---%0Aconfig%3A%0A%20%20layout%3A%20dagre%0A%20%20theme%3A%20neural%0A---%0AsequenceDiagram%0A%20%20%20%20autonumber%0A%20%20%20%20participant%20C%20as%20Client%0A%20%20%20%20participant%20S%20as%20Service%0A%0A%20%20%20%20Note%20over%20S%3A%20Initialization%20Phase%0A%20%20%20%20S-%3E%3ES%3A%20init%0A%20%20%20%20create%20participant%20RC%20as%20Runtime%20(CLIP)%0A%20%20%20%20S-%3E%3ERC%3A%20Load%20Text%20Encoder%0A%20%20%20%20create%20participant%20RU%20as%20Runtime%20(U-Net)%0A%20%20%20%20S-%3E%3ERU%3A%20Load%20Noise%20Predictor%0A%20%20%20%20create%20participant%20RV%20as%20Runtime%20(VAE)%0A%20%20%20%20S-%3E%3ERV%3A%20Load%20Decoder%0A%0A%20%20%20%20participant%20B%20as%20Backend%20(GPU%2FVRAM)%0A%0A%20%20%20%20RU-%3E%3EB%3A%20Allocate%20VRAM%20%2F%20Set%20Kernels%0A%20%20%20%20activate%20S%0A%20%20%20%20S-%3E%3ES%3A%20accept%20requests%0A%0A%20%20%20%20Note%20over%20C%2C%20B%3A%20Inference%20Request%0A%20%20%20%20activate%20C%0A%20%20%20%20C-%3E%3ES%3A%20%22Generate%20image%20of...%22%0A%0A%20%20%20%20S-%3E%3ERC%3A%20run_encoder(text)%0A%20%20%20%20RC-%3E%3EB%3A%20compute(tensors)%0A%20%20%20%20B--%3E%3ERC%3A%20embeddings%0A%20%20%20%20RC--%3E%3ES%3A%20Concept%20Vector%0A%0A%20%20%20%20S-%3E%3ES%3A%20Init%20Latent%20Noise%20(z)%0A%0A%20%20%20%20loop%20%22%5BScheduler%20Timesteps%5D%22%0A%20%20%20%20%20%20%20%20S-%3E%3ERU%3A%20predict_noise(z%2C%20timestep%2C%20concept)%0A%20%20%20%20%20%20%20%20RU-%3E%3EB%3A%20compute(tensors)%0A%20%20%20%20%20%20%20%20B--%3E%3ERU%3A%20noise_tensors%0A%20%20%20%20%20%20%20%20RU--%3E%3ES%3A%20Predicted%20Pattern%20(%24%5Cepsilon%24)%0A%0A%20%20%20%20%20%20%20%20Note%20over%20S%3A%20Scheduler%20Logic%3A%20%3Cbr%2F%3E%20Clean%20z%20using%20Predicted%20Pattern%0A%20%20%20%20%20%20%20%20S-%3E%3ES%3A%20z%20%3D%20scheduler.step(z%2C%20%24%5Cepsilon%24)%0A%0A%20%20%20%20%20%20%20%20opt%20%22Optional%20Preview%22%0A%20%20%20%20%20%20%20%20%20%20%20%20S-%3E%3ERV%3A%20decode(z)%0A%20%20%20%20%20%20%20%20%20%20%20%20RV-%3E%3EB%3A%20compute(tensors)%0A%20%20%20%20%20%20%20%20%20%20%20%20B--%3E%3ERV%3A%20pixels%0A%20%20%20%20%20%20%20%20%20%20%20%20RV--%3E%3ES%3A%20image_data%0A%20%20%20%20%20%20%20%20%20%20%20%20S-%3E%3EC%3A%20stream(frame)%0A%20%20%20%20%20%20%20%20end%0A%20%20%20%20end%0A%0A%20%20%20%20S-%3E%3ERV%3A%20decode(final_z)%0A%20%20%20%20RV-%3E%3EB%3A%20compute(tensors)%0A%20%20%20%20B--%3E%3ERV%3A%20pixels%0A%20%20%20%20RV--%3E%3ES%3A%20Final%20Pixels%0A%20%20%20%20S-%3E%3EC%3A%20deliver(image)%0A%0A%20%20%20%20deactivate%20C%0A%20%20%20%20deactivate%20S --- :: Again, this is a conceptual flow. In production environments, the flow would be heavily optimized and thus look different. Still, fundamentally, this is what happens behind the scenes: #### Startup / Initialization Phase 1. The service initializes by identifying the specific diffusion model configuration, the required runtimes, and the optimal hardware backends available. 2. The service instantiates the first inference runtime to load the text encoder (CLIP), which is responsible for understanding the semantic meaning of the user's prompt. 3. The service instantiates a second runtime for the noise predictor (U-Net), the "brain" of the diffusion process that identifies patterns within random noise. 4. The service instantiates a third runtime for the decoder (VAE), which is used to translate mathematical representations (latents) into actual pixel maps. 5. The runtimes coordinate with the compute backend to allocate VRAM and prepare the specialized kernels needed for high-speed tensor math. 6. With all models loaded and the hardware prepared, the service opens its API or UI to begin accepting image generation requests from clients. #### Inference Request Processing 7. The service receives a natural language prompt from the client describing the image to be generated. 8. The service sends the prompt to the CLIP runtime to translate the string into a high-dimensional numerical representation (embeddings). 9. The CLIP runtime utilizes the backend to process the text, resulting in a "Concept Vector" that the other models can understand. 10. The result is a vector, which now acts as the permanent semantic anchor for the entire generation process. 11. The service receives this vector from the runtime 12. The service generates a tensor of completely random Gaussian noise (latents) at a smaller scale than the final image to serve as the "starting canvas." 13. The service starts the loop by passing the current noisy latents, the concept vector, and the current timestep to the U-Net runtime. 14. The U-Net runtime executes its graph on the backend to identify which parts of the current noise look like the requested concepts. 15. The execution results in a "pattern map" (predicted noise) representing the elements the model suggests should be removed to reveal the image. 16. The runtime passes this prediction back to the service for the next orchestration step. 17. The service uses the scheduler library to mathematically subtract a portion of the predicted noise from the current latents, resulting in a slightly "cleaner" version of the image. 18. If configured for streaming, the service sends the current intermediate latents to the VAE runtime for decoding. 19. The VAE runtime processes the mathematical latent on the backend to reconstruct a human-readable pixel map. 20. The runtime returns the raw image data (RGB pixels) to the service. 21. The service receives the frame and formats it for transmission. 22. The service pushes the low-quality preview frame to the client so the user can watch the image "emerge" from the noise. 23. Once the loop reaches the noise threshold or step limit, the service sends the final refined latent to the VAE runtime for high-quality reconstruction. 24. The VAE performs a final pass on the backend 25. The runtime execution results in the final pixel map 26. The service receives the final generated asset from the runtime. 27. The service performs any final post-processing (like PNG encoding) and delivers the completed image to the client, closing the session. ## Single-Shot Inference While the above flows relay on inference loops, many smaller models can get their work done using a single-shot inference. That means we don't have to do the above mentioned predict-next loop and get the results we need by calling the inference runtime just once. Consider the following categories of models: - **Classification** - “which class does this belong to?” - **Regression** - “what is the numerical value?” - **Ranking/Recommendation** - “rank these items from most to least relevant.” - **Similarity / Retrieval** - “which items are most similar?” - **Detection / Segmentation** - “where are the objects? / what is the mask?” - **Forecasting** - “what will the next value be?” - **Anomaly** - “is this point an outlier?” The steps to use any of those from our code are almost identical. At the initialization phase, we still need to pick a model, an inference runtime that can load it, potentially a compatible tokenizer, and a compute backend. At request processing time, we still need to preprocess the input, execute the computation, and postprocess the result. As not all models work with text and word tokenizers, let's see how other examples follow the same process. Say we have a `json` with some credit card transactions and want to check for possible fraud. Our input could be a `json` like the one below. ```json [ { "account_id": "ACC_STEADY_COFFEE", "history": [ {"month": 11, "day": 1, "dow": 1, "hour": 8, "min": 15, "amount": 4.50, ...}, {"month": 11, "day": 1, "dow": 1, "hour": 9, "min": 30, "amount": 12.00, ... }, ... ] }, ... ] ``` If we were to run a fraud detection model like [IBM's `GRU` or `LSTM` models](https://github.com/IBM/ai-on-z-fraud-detection){rel=""nofollow""}, we need to convert our data to a feature tensor during the preprocessing. The input shape of the model is `[7, 16, 220]`, meaning it is designed to process 7 batches of data simultaneously, where each batch contains a sequence of 16 transactions, and each transaction is represented by 220 features. So that's what the service needs to produce. ```json [ [ // batch 1 [f1, f2, ..., f220], // transaction 1 ... [f1, f2, ..., f220], // transaction 16 ], ... [ // batch 7 ... ] ] ``` Then the service can execute the model just once and get the scores. The output shape of `[7, 16, 1]` means the model generates results for 7 batches simultaneously, where each batch contains a single fraud score for each of the 16 transactions in the sequence. During post-processing, the service converts those scores to meaningful thresholds or labels Here is how the inference flow looks: ::mermaid --- :config: config code: ---%0Aconfig%3A%0A%20%20layout%3A%20dagre%0A%20%20theme%3A%20neural%0A---%0AsequenceDiagram%0A%20%20%20%20autonumber%0A%20%20%20%20participant%20C%20as%20Client%0A%20%20%20%20participant%20S%20as%20Service%0A%0A%20%20%20%20Note%20over%20S%3A%20Initialization%20Phase%0A%20%20%20%20S-%3E%3ES%3A%20init%0A%20%20%20%20create%20participant%20R%20as%20Runtime%0A%20%20%20%20S-%3E%3ER%3A%20Start%20runtime%20(Model%2C%20Preferred%20Backend)%0A%0A%20%20%20%20participant%20B%20as%20Backend%20(Compute%2FVRAM)%0A%0A%20%20%20%20R-%3E%3EB%3A%20Load%20Graph%20%26%20Allocate%20Memory%0A%20%20%20%20activate%20S%0A%20%20%20%20S-%3E%3ES%3A%20accept%20requests%0A%0A%20%20%20%20Note%20over%20C%2C%20B%3A%20Inference%20Request%0A%20%20%20%20activate%20C%0A%20%20%20%20C-%3E%3ES%3A%20Raw%20Data%20(e.g.%2C%20transactions.json)%0A%0A%20%20%20%20S-%3E%3ES%3A%20Featurize%20(Scale%20numbers%2C%20encode%20categories)%0A%0A%20%20%20%20S-%3E%3ER%3A%20run_inference(input_tensors)%0A%20%20%20%20R-%3E%3EB%3A%20compute(graph)%0A%0A%20%20%20%20B--%3E%3ER%3A%20raw_results%0A%20%20%20%20R--%3E%3ES%3A%20scores_matrix%0A%0A%20%20%20%20S-%3E%3ES%3A%20Post-process%20(Apply%20thresholds%2Flabels)%0A%20%20%20%20S-%3E%3EC%3A%20Analysis%20Report%0A%0A%20%20%20%20deactivate%20C%0A%20%20%20%20deactivate%20S --- :: Hopefully, the flow is simple enough and self-explanatory, but for the sake of consistency with the previous ones: #### Startup / Initialization Phase 1. The service prepares the environment and determines which model and backend are required for the task. 2. The service instantiates the inference runtime and provides the model artifact. 3. The runtime communicates with the compute backend to load the model's computation graph into memory and prepare for execution. 4. The service begins listening for data payloads from clients. #### Inference Request Processing 5. The client sends a dataset, such as a collection of transaction histories. 6. The service performs the "data preparation" step, constructing a `[7, 16, 220]` tensor 7. The service passes the prepared tensors to the runtime. 8. The runtime executes the graph on the backend. 9. The backend produces the `[7, 16, 1]` tensor with the scores. 10. The runtime sends the scores tensor to the service. 11. The service applies the business logic to the raw scores, such as labeling a high-probability score as "FRAUD" or a medium one as "SUSPECT." 12. The service returns the final report or categorized data to the client. ## Summary While AI models are often viewed as black boxes, their execution in production relies on a precise orchestration between specialized hardware, execution runtimes, and the services that wrap them. In the academic Python world, these boundaries are often blurred; a script that produces a correct result may not offer a clear path for decomposition or scaling. It was only after reproducing these behaviors in language stacks like Java and TypeScript—using unified runtimes like ONNX—that I was able to establish a clear mental model of how these pieces fit together. This post deconstructs that connection, illustrating the interplay between **compute backends**, **inference runtimes**, and **services** to help you architect systems that are both performant and truly scalable. --- ::note --- color: soft icon: mdi-light-book-multiple title: AI for Application Developers Series --- The post is part of the [AI for Application Developers](https://MilenDyankov.com/blog/2026/03/AI-for-Application-Developers) series - my personal notes on various AI topics converted to blog posts. Please do not hesitate to **correct** me if I got something wrong, **contribute** if something is missing, **ask** me to clarify or simply **share** your experience and views. ::